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/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;
diff --git a/androidx/app/slice/builders/MessagingSliceBuilder.java b/androidx/app/slice/builders/MessagingSliceBuilder.java
new file mode 100644
index 0000000..2918904
--- /dev/null
+++ b/androidx/app/slice/builders/MessagingSliceBuilder.java
@@ -0,0 +1,110 @@
+/*
+ * 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 androidx.app.slice.builders;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.app.slice.Slice;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Builder to construct slice content in a messaging format.
+ */
+public class MessagingSliceBuilder extends TemplateSliceBuilder {
+
+    /**
+     * The maximum number of messages that will be retained in the Slice itself (the
+     * number displayed is up to the platform).
+     */
+    public static final int MAXIMUM_RETAINED_MESSAGES = 50;
+
+    public MessagingSliceBuilder(@NonNull Uri uri) {
+        super(uri);
+    }
+
+    /**
+     * Create a {@link MessageBuilder} that will be added to this slice when
+     * {@link MessageBuilder#finish()} is called.
+     * @return a new message builder
+     */
+    public MessageBuilder startMessage() {
+        return new MessageBuilder(this);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public void apply(Slice.Builder builder) {
+    }
+
+    @Override
+    public void add(SubTemplateSliceBuilder builder) {
+        getBuilder().addSubSlice(builder.build());
+    }
+
+    /**
+     * Builder for adding a message to {@link MessagingSliceBuilder}.
+     */
+    public static final class MessageBuilder
+            extends SubTemplateSliceBuilder<MessagingSliceBuilder> {
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public MessageBuilder(MessagingSliceBuilder parent) {
+            super(parent.createChildBuilder(), parent);
+            getBuilder().addHints(Slice.HINT_MESSAGE);
+        }
+
+        /**
+         * Add the icon used to display contact in the messaging experience
+         */
+        public MessageBuilder addSource(Icon source) {
+            getBuilder().addIcon(source, Slice.HINT_SOURCE);
+            return this;
+        }
+
+        /**
+         * Add the text to be used for this message.
+         */
+        public MessageBuilder addText(CharSequence text) {
+            getBuilder().addText(text);
+            return this;
+        }
+
+        /**
+         * Add the time at which this message arrived in ms since Unix epoch
+         */
+        public MessageBuilder addTimestamp(long timestamp) {
+            getBuilder().addTimestamp(timestamp);
+            return this;
+        }
+
+        /**
+         * Complete the construction of this message and add it to the parent builder.
+         * @return the parent builder so construction can continue.
+         */
+        public MessagingSliceBuilder endMessage() {
+            return finish();
+        }
+    }
+}
diff --git a/androidx/app/slice/builders/SliceHints.java b/androidx/app/slice/builders/SliceHints.java
new file mode 100644
index 0000000..5db1219
--- /dev/null
+++ b/androidx/app/slice/builders/SliceHints.java
@@ -0,0 +1,50 @@
+/*
+ * 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 androidx.app.slice.builders;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.app.slice.Slice;
+import android.app.slice.widget.SliceView;
+import android.support.annotation.RestrictTo;
+
+
+/**
+ * Temporary class to contain hint constants for slices to be used.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class SliceHints {
+    /**
+     * Hint to indicate that this content has a toggle action associated with it. To indicate that
+     * the toggle is on, use {@link Slice#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.
+     */
+    public static final String HINT_TOGGLE = "toggle";
+
+    /**
+     * Key to retrieve an extra added to an intent when a control is changed.
+     */
+    public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
+    /**
+     * 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.
+     */
+    public static final String HINT_HIDDEN = "hidden";
+}
diff --git a/androidx/app/slice/builders/TemplateSliceBuilder.java b/androidx/app/slice/builders/TemplateSliceBuilder.java
new file mode 100644
index 0000000..61fea7f
--- /dev/null
+++ b/androidx/app/slice/builders/TemplateSliceBuilder.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 androidx.app.slice.builders;
+
+
+import android.app.slice.Slice;
+import android.net.Uri;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Base class of builders of various template types.
+ */
+public abstract class TemplateSliceBuilder {
+
+    private final Slice.Builder mSliceBuilder;
+
+    public TemplateSliceBuilder(Uri uri) {
+        mSliceBuilder = new Slice.Builder(uri);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public Slice.Builder getBuilder() {
+        return mSliceBuilder;
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public Slice.Builder createChildBuilder() {
+        return new Slice.Builder(mSliceBuilder);
+    }
+
+    /**
+     * Construct the slice.
+     */
+    public Slice build() {
+        apply(mSliceBuilder);
+        return mSliceBuilder.build();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public abstract void apply(Slice.Builder builder);
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public abstract void add(SubTemplateSliceBuilder builder);
+
+    /**
+     * Base class of builders for sub-slices of {@link TemplateSliceBuilder}s.
+     * @param <T> Type of parent
+     */
+    public abstract static class SubTemplateSliceBuilder<T extends TemplateSliceBuilder> {
+
+        private final Slice.Builder mBuilder;
+        private final T mParent;
+
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public SubTemplateSliceBuilder(Slice.Builder builder, T parent) {
+            mBuilder = builder;
+            mParent = parent;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public SubTemplateSliceBuilder(Slice.Builder builder) {
+            mBuilder = builder;
+            mParent = null;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public Slice.Builder getBuilder() {
+            return mBuilder;
+        }
+
+        /**
+         * Construct the slice.
+         */
+        public Slice build() {
+            return mBuilder.build();
+        }
+
+        /**
+         * Construct the slice and return to the parent object. If this object was not
+         * created from a {@link TemplateSliceBuilder} it will return null.
+         * @return parent builder
+         * @hide
+         */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        public T finish() {
+            if (mParent != null) {
+                mParent.add(this);
+            }
+            return mParent;
+        }
+    }
+}
diff --git a/androidx/app/slice/core/SliceQuery.java b/androidx/app/slice/core/SliceQuery.java
new file mode 100644
index 0000000..e1430d0
--- /dev/null
+++ b/androidx/app/slice/core/SliceQuery.java
@@ -0,0 +1,289 @@
+/*
+ * 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 androidx.app.slice.core;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.support.annotation.RestrictTo;
+import android.text.TextUtils;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import androidx.app.slice.builders.SliceHints;
+
+/**
+ * Utilities for finding content within a Slice.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class SliceQuery {
+
+    /**
+     * @return Whether this item is appropriate to be considered a "start" item, i.e. go in the
+     *         front slot of a small slice.
+     */
+    public static boolean isStartType(SliceItem item) {
+        final int type = item.getType();
+        return !item.hasHint(SliceHints.HINT_TOGGLE)
+                && ((type == SliceItem.TYPE_ACTION && (find(item, SliceItem.TYPE_IMAGE) != null))
+                || type == SliceItem.TYPE_IMAGE
+                || type == SliceItem.TYPE_TIMESTAMP);
+    }
+
+    /**
+     * @return Finds the first slice that has non-slice children.
+     */
+    public static SliceItem findFirstSlice(SliceItem slice) {
+        if (slice.getType() != SliceItem.TYPE_SLICE) {
+            return slice;
+        }
+        List<SliceItem> items = slice.getSlice().getItems();
+        for (int i = 0; i < items.size(); i++) {
+            if (items.get(i).getType() == SliceItem.TYPE_SLICE) {
+                SliceItem childSlice = items.get(i);
+                return findFirstSlice(childSlice);
+            } else {
+                // Doesn't have slice children so return it
+                return slice;
+            }
+        }
+        // Slices all the way down, just return it
+        return slice;
+    }
+
+    /**
+     * @return Whether this item is a simple action, i.e. an action that only has an icon.
+     */
+    public static boolean isSimpleAction(SliceItem item) {
+        if (item.getType() == SliceItem.TYPE_ACTION) {
+            List<SliceItem> items = item.getSlice().getItems();
+            boolean hasImage = false;
+            for (int i = 0; i < items.size(); i++) {
+                SliceItem child = items.get(i);
+                if (child.getType() == SliceItem.TYPE_IMAGE && !hasImage) {
+                    hasImage = true;
+                } else if (child.getType() == SliceItem.TYPE_COLOR) {
+                    continue;
+                } else {
+                    return false;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     */
+    public static boolean hasAnyHints(SliceItem item, String... hints) {
+        if (hints == null) return false;
+        List<String> itemHints = item.getHints();
+        for (String hint : hints) {
+            if (itemHints.contains(hint)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     */
+    public static boolean hasHints(SliceItem item, String... hints) {
+        if (hints == null) return true;
+        List<String> itemHints = item.getHints();
+        for (String hint : hints) {
+            if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     */
+    public static boolean hasHints(Slice item, String... hints) {
+        if (hints == null) return true;
+        List<String> itemHints = item.getHints();
+        for (String hint : hints) {
+            if (!TextUtils.isEmpty(hint) && !itemHints.contains(hint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     */
+    public static SliceItem getPrimaryIcon(Slice slice) {
+        for (SliceItem item : slice.getItems()) {
+            if (item.getType() == SliceItem.TYPE_IMAGE) {
+                return item;
+            }
+            if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+                    && !item.hasHint(Slice.HINT_ACTIONS)
+                    && !item.hasHint(Slice.HINT_LIST_ITEM)
+                    && (item.getType() != SliceItem.TYPE_ACTION)) {
+                SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+                if (icon != null) {
+                    return icon;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     */
+    public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
+        SliceItem ret = null;
+        while (ret == null && list.size() != 0) {
+            SliceItem remove = list.remove(0);
+            if (!contains(container, remove)) {
+                ret = remove;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     */
+    private static boolean contains(SliceItem container, SliceItem item) {
+        if (container == null || item == null) return false;
+        return stream(container).filter(s -> (s == item)).findAny().isPresent();
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type) {
+        return findAll(s, type, (String[]) null, null);
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(Slice s, int type, String hints, String nonHints) {
+        return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
+        return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(Slice s, int type, String[] hints,
+            String[] nonHints) {
+        return stream(s).filter(item -> (type == -1 || item.getType() == type)
+                && (hasHints(item, hints) && !hasAnyHints(item, nonHints)))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
+            String[] nonHints) {
+        return stream(s).filter(item -> (type == -1 || item.getType() == type)
+                && (hasHints(item, hints) && !hasAnyHints(item, nonHints)))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     */
+    public static SliceItem find(Slice s, int type, String hints, String nonHints) {
+        return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static SliceItem find(Slice s, int type) {
+        return find(s, type, (String[]) null, null);
+    }
+
+    /**
+     */
+    public static SliceItem find(SliceItem s, int type) {
+        return find(s, type, (String[]) null, null);
+    }
+
+    /**
+     */
+    public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
+        return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     */
+    public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
+        List<String> h = s.getHints();
+        return stream(s).filter(item -> (item.getType() == type || type == -1)
+                && (hasHints(item, hints) && !hasAnyHints(item, nonHints))).findFirst()
+                .orElse(null);
+    }
+
+    /**
+     */
+    public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
+        return stream(s).filter(item -> (item.getType() == type || type == -1)
+                && (hasHints(item, hints) && !hasAnyHints(item, nonHints))).findFirst()
+                .orElse(null);
+    }
+
+    /**
+     */
+    public static Stream<SliceItem> stream(SliceItem slice) {
+        Queue<SliceItem> items = new LinkedList();
+        items.add(slice);
+        return getSliceItemStream(items);
+    }
+
+    /**
+     */
+    public static Stream<SliceItem> stream(Slice slice) {
+        Queue<SliceItem> items = new LinkedList();
+        items.addAll(slice.getItems());
+        return getSliceItemStream(items);
+    }
+
+    /**
+     */
+    private static Stream<SliceItem> getSliceItemStream(Queue<SliceItem> items) {
+        Iterator<SliceItem> iterator = new Iterator<SliceItem>() {
+            @Override
+            public boolean hasNext() {
+                return items.size() != 0;
+            }
+
+            @Override
+            public SliceItem next() {
+                SliceItem item = items.poll();
+                if (item.getType() == SliceItem.TYPE_SLICE
+                        || item.getType() == SliceItem.TYPE_ACTION) {
+                    items.addAll(item.getSlice().getItems());
+                }
+                return item;
+            }
+        };
+        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
+    }
+}
diff --git a/androidx/app/slice/widget/ActionRow.java b/androidx/app/slice/widget/ActionRow.java
new file mode 100644
index 0000000..0a09620
--- /dev/null
+++ b/androidx/app/slice/widget/ActionRow.java
@@ -0,0 +1,204 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.os.AsyncTask;
+import android.support.annotation.RestrictTo;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class ActionRow extends FrameLayout {
+
+    private static final int MAX_ACTIONS = 5;
+    private final int mSize;
+    private final int mIconPadding;
+    private final LinearLayout mActionsGroup;
+    private final boolean mFullActions;
+    private int mColor = Color.BLACK;
+
+    public ActionRow(Context context, boolean fullActions) {
+        super(context);
+        mFullActions = fullActions;
+        mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
+                context.getResources().getDisplayMetrics());
+        mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
+                context.getResources().getDisplayMetrics());
+        mActionsGroup = new LinearLayout(context);
+        mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
+        mActionsGroup.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        addView(mActionsGroup);
+    }
+
+    private void setColor(int color) {
+        mColor = color;
+        for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+            View view = mActionsGroup.getChildAt(i);
+            SliceItem item = (SliceItem) view.getTag();
+            boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
+            if (tint) {
+                ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
+            }
+        }
+    }
+
+    private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
+        ImageView imageView = new ImageView(getContext());
+        imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+        imageView.setScaleType(ScaleType.FIT_CENTER);
+        imageView.setImageIcon(icon);
+        if (allowTint) {
+            imageView.setImageTintList(ColorStateList.valueOf(mColor));
+        }
+        imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
+                android.R.attr.selectableItemBackground));
+        imageView.setTag(image);
+        addAction(imageView);
+        return imageView;
+    }
+
+    /**
+     * Set the actions and color for this action row.
+     */
+    public void setActions(SliceItem actionRow, SliceItem defColor) {
+        removeAllViews();
+        mActionsGroup.removeAllViews();
+        addView(mActionsGroup);
+
+        SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
+        if (color == null) {
+            color = defColor;
+        }
+        if (color != null) {
+            setColor(color.getColor());
+        }
+        SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
+            if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
+                return;
+            }
+            SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
+            if (image == null) {
+                return;
+            }
+            boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
+            SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
+            if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
+                addAction(image.getIcon(), tint, image).setOnClickListener(
+                        v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
+                createRemoteInputView(mColor, getContext());
+            } else {
+                addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                action.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+            }
+        });
+        setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
+    }
+
+    private void addAction(View child) {
+        mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
+    }
+
+    private void createRemoteInputView(int color, Context context) {
+        View riv = RemoteInputView.inflate(context, this);
+        riv.setVisibility(View.INVISIBLE);
+        addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        riv.setBackgroundColor(color);
+    }
+
+    private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
+            RemoteInput input) {
+        if (input == null) {
+            return false;
+        }
+
+        ViewParent p = view.getParent().getParent();
+        RemoteInputView riv = null;
+        while (p != null) {
+            if (p instanceof View) {
+                View pv = (View) p;
+                riv = findRemoteInputView(pv);
+                if (riv != null) {
+                    break;
+                }
+            }
+            p = p.getParent();
+        }
+        if (riv == null) {
+            return false;
+        }
+
+        int width = view.getWidth();
+        if (view instanceof TextView) {
+            // Center the reveal on the text which might be off-center from the TextView
+            TextView tv = (TextView) view;
+            if (tv.getLayout() != null) {
+                int innerWidth = (int) tv.getLayout().getLineWidth(0);
+                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+                width = Math.min(width, innerWidth);
+            }
+        }
+        int cx = view.getLeft() + width / 2;
+        int cy = view.getTop() + view.getHeight() / 2;
+        int w = riv.getWidth();
+        int h = riv.getHeight();
+        int r = Math.max(
+                Math.max(cx + cy, cx + (h - cy)),
+                Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+        riv.setRevealParameters(cx, cy, r);
+        riv.setPendingIntent(pendingIntent);
+        riv.setRemoteInput(new RemoteInput[] {
+                input
+        }, input);
+        riv.focusAnimated();
+        return true;
+    }
+
+    private RemoteInputView findRemoteInputView(View v) {
+        if (v == null) {
+            return null;
+        }
+        return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+    }
+}
diff --git a/androidx/app/slice/widget/GridView.java b/androidx/app/slice/widget/GridView.java
new file mode 100644
index 0000000..c320c72
--- /dev/null
+++ b/androidx/app/slice/widget/GridView.java
@@ -0,0 +1,185 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.graphics.Color;
+import android.support.annotation.RestrictTo;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class GridView extends LinearLayout implements LargeSliceAdapter.SliceListView {
+
+    private static final String TAG = "GridView";
+
+    private static final int MAX_IMAGES = 3;
+    private static final int MAX_ALL = 5;
+    private boolean mIsAllImages;
+
+    public GridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mIsAllImages) {
+            int width = MeasureSpec.getSize(widthMeasureSpec);
+            int height = width / getChildCount();
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+            getLayoutParams().height = height;
+            for (int i = 0; i < getChildCount(); i++) {
+                getChildAt(i).getLayoutParams().height = height;
+            }
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        mIsAllImages = true;
+        removeAllViews();
+        int total = 1;
+        if (slice.getType() == SliceItem.TYPE_SLICE) {
+            List<SliceItem> items = slice.getSlice().getItems();
+            total = items.size();
+            for (int i = 0; i < total; i++) {
+                SliceItem item = items.get(i);
+                if (isFull()) {
+                    continue;
+                }
+                if (!addItem(item)) {
+                    mIsAllImages = false;
+                }
+            }
+        } else {
+            if (!isFull()) {
+                if (!addItem(slice)) {
+                    mIsAllImages = false;
+                }
+            }
+        }
+        if (total > getChildCount() && mIsAllImages) {
+            addExtraCount(total - getChildCount());
+        }
+    }
+
+    private void addExtraCount(int numExtra) {
+        View last = getChildAt(getChildCount() - 1);
+        FrameLayout frame = new FrameLayout(getContext());
+        frame.setLayoutParams(last.getLayoutParams());
+
+        removeView(last);
+        frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        TextView v = new TextView(getContext());
+        v.setTextColor(Color.WHITE);
+        v.setBackgroundColor(0x4d000000);
+        v.setText(getResources().getString(R.string.abc_slice_more_content, numExtra));
+        v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
+        v.setGravity(Gravity.CENTER);
+        frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        addView(frame);
+    }
+
+    private boolean isFull() {
+        return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
+    }
+
+    /**
+     * Returns true if this item is just an image.
+     */
+    private boolean addItem(SliceItem item) {
+        if (item.getType() == SliceItem.TYPE_IMAGE) {
+            ImageView v = new ImageView(getContext());
+            v.setImageIcon(item.getIcon());
+            v.setScaleType(ScaleType.CENTER_CROP);
+            addView(v, new LayoutParams(0, MATCH_PARENT, 1));
+            return true;
+        } else {
+            LinearLayout v = new LinearLayout(getContext());
+            int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    12, getContext().getResources().getDisplayMetrics());
+            v.setPadding(0, s, 0, 0);
+            v.setOrientation(LinearLayout.VERTICAL);
+            v.setGravity(Gravity.CENTER_HORIZONTAL);
+            // TODO: Unify sporadic inflates that happen throughout the code.
+            ArrayList<SliceItem> items = new ArrayList<>();
+            if (item.getType() == SliceItem.TYPE_SLICE) {
+                items.addAll(item.getSlice().getItems());
+            }
+            items.forEach(i -> {
+                Context context = getContext();
+                switch (i.getType()) {
+                    case SliceItem.TYPE_TEXT:
+                        boolean title = false;
+                        if ((SliceQuery.hasAnyHints(item, new String[] {
+                                Slice.HINT_LARGE, Slice.HINT_TITLE
+                        }))) {
+                            title = true;
+                        }
+                        TextView tv = (TextView) LayoutInflater.from(context).inflate(title
+                                ? R.layout.abc_slice_title : R.layout.abc_slice_secondary_text,
+                                null);
+                        tv.setText(i.getText());
+                        v.addView(tv);
+                        break;
+                    case SliceItem.TYPE_IMAGE:
+                        ImageView iv = new ImageView(context);
+                        iv.setImageIcon(i.getIcon());
+                        if (item.hasHint(Slice.HINT_LARGE)) {
+                            iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+                        } else {
+                            int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                                    48, context.getResources().getDisplayMetrics());
+                            iv.setLayoutParams(new LayoutParams(size, size));
+                        }
+                        v.addView(iv);
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        // TODO: Support color to tint stuff here.
+                        break;
+                }
+            });
+            addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
+            return false;
+        }
+    }
+}
diff --git a/androidx/app/slice/widget/LargeSliceAdapter.java b/androidx/app/slice/widget/LargeSliceAdapter.java
new file mode 100644
index 0000000..ff513c5
--- /dev/null
+++ b/androidx/app/slice/widget/LargeSliceAdapter.java
@@ -0,0 +1,211 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class LargeSliceAdapter extends RecyclerView.Adapter<LargeSliceAdapter.SliceViewHolder> {
+
+    public static final int TYPE_DEFAULT       = 1;
+    public static final int TYPE_HEADER        = 2;
+    public static final int TYPE_GRID          = 3;
+    public static final int TYPE_MESSAGE       = 4;
+    public static final int TYPE_MESSAGE_LOCAL = 5;
+
+    private final IdGenerator mIdGen = new IdGenerator();
+    private final Context mContext;
+    private List<SliceWrapper> mSlices = new ArrayList<>();
+    private SliceItem mColor;
+
+    public LargeSliceAdapter(Context context) {
+        mContext = context;
+        setHasStableIds(true);
+    }
+
+    /**
+     * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
+     */
+    public void setSliceItems(List<SliceItem> slices, SliceItem color) {
+        mColor = color;
+        mIdGen.resetUsage();
+        mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
+                .collect(Collectors.toList());
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View v = inflateForType(viewType);
+        v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        return new SliceViewHolder(v);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mSlices.get(position).mType;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mSlices.get(position).mId;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSlices.size();
+    }
+
+    @Override
+    public void onBindViewHolder(SliceViewHolder holder, int position) {
+        SliceWrapper slice = mSlices.get(position);
+        if (holder.mSliceView != null) {
+            holder.mSliceView.setColor(mColor);
+            holder.mSliceView.setSliceItem(slice.mItem);
+        }
+    }
+
+    private View inflateForType(int viewType) {
+        switch (viewType) {
+            case TYPE_GRID:
+                return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_grid, null);
+            case TYPE_MESSAGE:
+                return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message, null);
+            case TYPE_MESSAGE_LOCAL:
+                return LayoutInflater.from(mContext).inflate(R.layout.abc_slice_message_local,
+                        null);
+        }
+        return new SmallTemplateView(mContext);
+    }
+
+    protected static class SliceWrapper {
+        private final SliceItem mItem;
+        private final int mType;
+        private final long mId;
+
+        public SliceWrapper(SliceItem item, IdGenerator idGen) {
+            mItem = item;
+            mType = getType(item);
+            mId = idGen.getId(item);
+        }
+
+        public static int getType(SliceItem item) {
+            if (item.hasHint(Slice.HINT_MESSAGE)) {
+                // TODO: Better way to determine me or not? Something more like Messaging style.
+                if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
+                    return TYPE_MESSAGE;
+                } else {
+                    return TYPE_MESSAGE_LOCAL;
+                }
+            }
+            if (item.hasHint(Slice.HINT_HORIZONTAL)) {
+                return TYPE_GRID;
+            }
+            return TYPE_DEFAULT;
+        }
+    }
+
+    /**
+     * A {@link RecyclerView.ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
+     */
+    public static class SliceViewHolder extends RecyclerView.ViewHolder {
+        public final SliceListView mSliceView;
+
+        public SliceViewHolder(View itemView) {
+            super(itemView);
+            mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
+        }
+    }
+
+    /**
+     * View slices being displayed in {@link LargeSliceAdapter}.
+     */
+    public interface SliceListView {
+        /**
+         * Set the slice item for this view.
+         */
+        void setSliceItem(SliceItem slice);
+
+        /**
+         * Set the color for the items in this view.
+         */
+        default void setColor(SliceItem color) {
+
+        }
+    }
+
+    private static class IdGenerator {
+        private long mNextLong = 0;
+        private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
+        private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
+
+        public long getId(SliceItem item) {
+            String str = genString(item);
+            if (!mCurrentIds.containsKey(str)) {
+                mCurrentIds.put(str, mNextLong++);
+            }
+            long id = mCurrentIds.get(str);
+            int index = mUsedIds.getOrDefault(str, 0);
+            mUsedIds.put(str, index + 1);
+            return id + index * 10000;
+        }
+
+        private String genString(SliceItem item) {
+            StringBuilder builder = new StringBuilder();
+            SliceQuery.stream(item).forEach(i -> {
+                builder.append(i.getType());
+                //i.removeHint(Slice.HINT_SELECTED);
+                builder.append(i.getHints());
+                switch (i.getType()) {
+                    case SliceItem.TYPE_IMAGE:
+                        builder.append(i.getIcon());
+                        break;
+                    case SliceItem.TYPE_TEXT:
+                        builder.append(i.getText());
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        builder.append(i.getColor());
+                        break;
+                }
+            });
+            return builder.toString();
+        }
+
+        public void resetUsage() {
+            mUsedIds.clear();
+        }
+    }
+}
diff --git a/androidx/app/slice/widget/LargeTemplateView.java b/androidx/app/slice/widget/LargeTemplateView.java
new file mode 100644
index 0000000..c5e82aa
--- /dev/null
+++ b/androidx/app/slice/widget/LargeTemplateView.java
@@ -0,0 +1,121 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class LargeTemplateView extends SliceView.SliceModeView {
+
+    private final LargeSliceAdapter mAdapter;
+    private final RecyclerView mRecyclerView;
+    private final int mDefaultHeight;
+    private final int mMaxHeight;
+    private Slice mSlice;
+    private boolean mIsScrollable;
+
+    public LargeTemplateView(Context context) {
+        super(context);
+
+        mRecyclerView = new RecyclerView(getContext());
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        mAdapter = new LargeSliceAdapter(context);
+        mRecyclerView.setAdapter(mAdapter);
+        addView(mRecyclerView);
+        mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+        mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+    }
+
+    @Override
+    public @SliceView.SliceMode int getMode() {
+        return SliceView.MODE_LARGE;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mRecyclerView.getMeasuredHeight() > mMaxHeight
+                || (mSlice != null && SliceQuery.hasHints(mSlice, Slice.HINT_PARTIAL))) {
+            mRecyclerView.getLayoutParams().height = mDefaultHeight;
+        } else {
+            mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        mSlice = slice;
+        List<SliceItem> items = new ArrayList<>();
+        boolean[] hasHeader = new boolean[1];
+        if (SliceQuery.hasHints(slice, Slice.HINT_LIST)) {
+            addList(slice, items);
+        } else {
+            slice.getItems().forEach(item -> {
+                if (item.hasHint(Slice.HINT_ACTIONS)) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_COLOR) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_SLICE
+                        && item.hasHint(Slice.HINT_LIST)) {
+                    addList(item.getSlice(), items);
+                } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
+                    items.add(item);
+                } else if (!hasHeader[0]) {
+                    hasHeader[0] = true;
+                    items.add(0, item);
+                } else {
+                    items.add(item);
+                }
+            });
+        }
+        mAdapter.setSliceItems(items, color);
+    }
+
+    private void addList(Slice slice, List<SliceItem> items) {
+        List<SliceItem> sliceItems = slice.getItems();
+        items.addAll(sliceItems);
+    }
+
+    /**
+     * Whether or not the content in this template should be scrollable.
+     */
+    public void setScrollable(boolean isScrollable) {
+        // TODO -- restrict / enable how much this view can show
+        mIsScrollable = isScrollable;
+    }
+}
diff --git a/androidx/app/slice/widget/MessageView.java b/androidx/app/slice/widget/MessageView.java
new file mode 100644
index 0000000..9db35bb
--- /dev/null
+++ b/androidx/app/slice/widget/MessageView.java
@@ -0,0 +1,79 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.RestrictTo;
+import android.text.SpannableStringBuilder;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.app.slice.core.SliceQuery;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class MessageView extends LinearLayout implements LargeSliceAdapter.SliceListView {
+
+    private TextView mDetails;
+    private ImageView mIcon;
+
+    public MessageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDetails = findViewById(android.R.id.summary);
+        mIcon = findViewById(android.R.id.icon);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
+        if (source != null) {
+            final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    24, getContext().getResources().getDisplayMetrics());
+            // TODO try and turn this into a drawable
+            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            Drawable d = source.getIcon().loadDrawable(getContext());
+            d.setBounds(0, 0, iconSize, iconSize);
+            d.draw(iconCanvas);
+            mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
+        }
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
+            if (builder.length() != 0) {
+                builder.append('\n');
+            }
+            builder.append(text.getText());
+        });
+        mDetails.setText(builder.toString());
+    }
+
+}
diff --git a/androidx/app/slice/widget/RemoteInputView.java b/androidx/app/slice/widget/RemoteInputView.java
new file mode 100644
index 0000000..9d45501
--- /dev/null
+++ b/androidx/app/slice/widget/RemoteInputView.java
@@ -0,0 +1,423 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.animation.Animator;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.RestrictTo;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.app.slice.view.R;
+
+/**
+ * Host for the remote input.
+ *
+ * @hide
+ */
+// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
+
+    private static final String TAG = "RemoteInput";
+
+    /**
+     * A marker object that let's us easily find views of this class.
+     */
+    public static final Object VIEW_TAG = new Object();
+
+    private RemoteEditText mEditText;
+    private ImageButton mSendButton;
+    private ProgressBar mProgressBar;
+    private PendingIntent mPendingIntent;
+    private RemoteInput[] mRemoteInputs;
+    private RemoteInput mRemoteInput;
+
+    private int mRevealCx;
+    private int mRevealCy;
+    private int mRevealR;
+    private boolean mResetting;
+
+    public RemoteInputView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mProgressBar = findViewById(R.id.remote_input_progress);
+        mSendButton = findViewById(R.id.remote_input_send);
+        mSendButton.setOnClickListener(this);
+
+        mEditText = (RemoteEditText) getChildAt(0);
+        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                final boolean isSoftImeEvent = event == null
+                        && (actionId == EditorInfo.IME_ACTION_DONE
+                                || actionId == EditorInfo.IME_ACTION_NEXT
+                                || actionId == EditorInfo.IME_ACTION_SEND);
+                final boolean isKeyboardEnterKey = event != null
+                        && isConfirmKey(event.getKeyCode())
+                        && event.getAction() == KeyEvent.ACTION_DOWN;
+
+                if (isSoftImeEvent || isKeyboardEnterKey) {
+                    if (mEditText.length() > 0) {
+                        sendRemoteInput();
+                    }
+                    // Consume action to prevent IME from closing.
+                    return true;
+                }
+                return false;
+            }
+        });
+        mEditText.addTextChangedListener(this);
+        mEditText.setInnerFocusable(false);
+        mEditText.mRemoteInputView = this;
+    }
+
+    private void sendRemoteInput() {
+        Bundle results = new Bundle();
+        results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
+                results);
+
+        mEditText.setEnabled(false);
+        mSendButton.setVisibility(INVISIBLE);
+        mProgressBar.setVisibility(VISIBLE);
+        mEditText.mShowImeOnInputConnection = false;
+
+        // TODO: Figure out API for telling the system about slice interaction.
+        // Tell ShortcutManager that this package has been "activated".  ShortcutManager
+        // will reset the throttling for this package.
+        // Strictly speaking, the intent receiver may be different from the intent creator,
+        // but that's an edge case, and also because we can't always know which package will receive
+        // an intent, so we just reset for the creator.
+        //getContext().getSystemService(ShortcutManager.class).onApplicationActive(
+        //        mPendingIntent.getCreatorPackage(),
+        //        getContext().getUserId());
+
+        try {
+            mPendingIntent.send(getContext(), 0, fillInIntent);
+            reset();
+        } catch (PendingIntent.CanceledException e) {
+            Log.i(TAG, "Unable to send remote input result", e);
+            Toast.makeText(getContext(), "Failure sending pending intent for inline reply :(",
+                    Toast.LENGTH_SHORT).show();
+            reset();
+        }
+    }
+
+    /**
+     * Creates a remote input view.
+     */
+    public static RemoteInputView inflate(Context context, ViewGroup root) {
+        RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
+                R.layout.abc_slice_remote_input, root, false);
+        v.setTag(VIEW_TAG);
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mSendButton) {
+            sendRemoteInput();
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        // We never want for a touch to escape to an outer view or one we covered.
+        return true;
+    }
+
+    private void onDefocus() {
+        setVisibility(INVISIBLE);
+    }
+
+    /**
+     * Set the pending intent for remote input.
+     */
+    public void setPendingIntent(PendingIntent pendingIntent) {
+        mPendingIntent = pendingIntent;
+    }
+
+    /**
+     * Set the remote inputs for this view.
+     */
+    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+        mRemoteInputs = remoteInputs;
+        mRemoteInput = remoteInput;
+        mEditText.setHint(mRemoteInput.getLabel());
+    }
+
+    /**
+     * Focuses the remote input view.
+     */
+    public void focusAnimated() {
+        if (getVisibility() != VISIBLE) {
+            Animator animator = ViewAnimationUtils.createCircularReveal(
+                    this, mRevealCx, mRevealCy, 0, mRevealR);
+            animator.setDuration(200);
+            animator.start();
+        }
+        focus();
+    }
+
+    private void focus() {
+        setVisibility(VISIBLE);
+        mEditText.setInnerFocusable(true);
+        mEditText.mShowImeOnInputConnection = true;
+        mEditText.setSelection(mEditText.getText().length());
+        mEditText.requestFocus();
+        updateSendButton();
+    }
+
+    private void reset() {
+        mResetting = true;
+
+        mEditText.getText().clear();
+        mEditText.setEnabled(true);
+        mSendButton.setVisibility(VISIBLE);
+        mProgressBar.setVisibility(INVISIBLE);
+        updateSendButton();
+        onDefocus();
+
+        mResetting = false;
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (mResetting && child == mEditText) {
+            // Suppress text events if it happens during resetting. Ideally this would be
+            // suppressed by the text view not being shown, but that doesn't work here because it
+            // needs to stay visible for the animation.
+            return false;
+        }
+        return super.onRequestSendAccessibilityEvent(child, event);
+    }
+
+    private void updateSendButton() {
+        mSendButton.setEnabled(mEditText.getText().length() != 0);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        updateSendButton();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setRevealParameters(int cx, int cy, int r) {
+        mRevealCx = cx;
+        mRevealCy = cy;
+        mRevealR = r;
+    }
+
+    @Override
+    public void dispatchStartTemporaryDetach() {
+        super.dispatchStartTemporaryDetach();
+        // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
+        // won't lose IME focus.
+        detachViewFromParent(mEditText);
+    }
+
+    @Override
+    public void dispatchFinishTemporaryDetach() {
+        if (isAttachedToWindow()) {
+            attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
+        } else {
+            removeDetachedView(mEditText, false /* animate */);
+        }
+        super.dispatchFinishTemporaryDetach();
+    }
+
+    /**
+     * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
+     * whenever the user navigates away from it or it becomes invisible.
+     */
+    public static class RemoteEditText extends EditText {
+
+        private final Drawable mBackground;
+        private RemoteInputView mRemoteInputView;
+        boolean mShowImeOnInputConnection;
+
+        public RemoteEditText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mBackground = getBackground();
+        }
+
+        private void defocusIfNeeded(boolean animate) {
+            if (mRemoteInputView != null || isTemporarilyDetached()) {
+                if (isTemporarilyDetached()) {
+                    // We might get reattached but then the other one of HUN / expanded might steal
+                    // our focus, so we'll need to save our text here.
+                }
+                return;
+            }
+            if (isFocusable() && isEnabled()) {
+                setInnerFocusable(false);
+                if (mRemoteInputView != null) {
+                    mRemoteInputView.onDefocus();
+                }
+                mShowImeOnInputConnection = false;
+            }
+        }
+
+        @Override
+        protected void onVisibilityChanged(View changedView, int visibility) {
+            super.onVisibilityChanged(changedView, visibility);
+
+            if (!isShown()) {
+                defocusIfNeeded(false /* animate */);
+            }
+        }
+
+        @Override
+        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+            super.onFocusChanged(focused, direction, previouslyFocusedRect);
+            if (!focused) {
+                defocusIfNeeded(true /* animate */);
+            }
+        }
+
+        @Override
+        public void getFocusedRect(Rect r) {
+            super.getFocusedRect(r);
+            r.top = getScrollY();
+            r.bottom = getScrollY() + (getBottom() - getTop());
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                // Eat the DOWN event here to prevent any default behavior.
+                return true;
+            }
+            return super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                defocusIfNeeded(true /* animate */);
+                return true;
+            }
+            return super.onKeyUp(keyCode, event);
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
+
+            if (mShowImeOnInputConnection && inputConnection != null) {
+                final InputMethodManager imm = getContext().getSystemService(
+                        InputMethodManager.class);
+                if (imm != null) {
+                    // onCreateInputConnection is called by InputMethodManager in the middle of
+                    // setting up the connection to the IME; wait with requesting the IME until that
+                    // work has completed.
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            imm.viewClicked(RemoteEditText.this);
+                            imm.showSoftInput(RemoteEditText.this, 0);
+                        }
+                    });
+                }
+            }
+
+            return inputConnection;
+        }
+
+        @Override
+        public void onCommitCompletion(CompletionInfo text) {
+            clearComposingText();
+            setText(text.getText());
+            setSelection(getText().length());
+        }
+
+        void setInnerFocusable(boolean focusable) {
+            setFocusableInTouchMode(focusable);
+            setFocusable(focusable);
+            setCursorVisible(focusable);
+
+            if (focusable) {
+                requestFocus();
+                setBackground(mBackground);
+            } else {
+                setBackground(null);
+            }
+
+        }
+    }
+
+    /** Whether key will, by default, trigger a click on the focused view.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public static final boolean isConfirmKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_SPACE:
+            case KeyEvent.KEYCODE_NUMPAD_ENTER:
+                return true;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/androidx/app/slice/widget/ShortcutView.java b/androidx/app/slice/widget/ShortcutView.java
new file mode 100644
index 0000000..b6e77cd
--- /dev/null
+++ b/androidx/app/slice/widget/ShortcutView.java
@@ -0,0 +1,174 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+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 android.support.annotation.RestrictTo;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class ShortcutView extends androidx.app.slice.widget.SliceView.SliceModeView {
+
+    private static final String TAG = "ShortcutView";
+
+    private Uri mUri;
+    private PendingIntent mAction;
+    private SliceItem mLabel;
+    private SliceItem mIcon;
+
+    private int mLargeIconSize;
+    private int mSmallIconSize;
+
+    public ShortcutView(Context context) {
+        super(context);
+        final Resources res = getResources();
+        mSmallIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_icon_size);
+        mLargeIconSize = res.getDimensionPixelSize(R.dimen.abc_slice_shortcut_size);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        removeAllViews();
+        determineShortcutItems(getContext(), slice);
+        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        if (colorItem == null) {
+            colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        }
+        // TODO: pick better default colour
+        final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
+        ShapeDrawable circle = new ShapeDrawable(new OvalShape());
+        circle.setTint(color);
+        setBackground(circle);
+        if (mIcon != null) {
+            final boolean isLarge = mIcon.hasHint(Slice.HINT_LARGE)
+                    || mIcon.hasHint(Slice.HINT_SOURCE);
+            final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
+            SliceViewUtil.createCircledIcon(getContext(), color, iconSize, mIcon.getIcon(),
+                    isLarge, this /* parent */);
+            mUri = slice.getUri();
+            setClickable(true);
+        } else {
+            setClickable(false);
+        }
+    }
+
+    @Override
+    public @SliceView.SliceMode int getMode() {
+        return SliceView.MODE_SHORTCUT;
+    }
+
+    @Override
+    public boolean performClick() {
+        if (!callOnClick()) {
+            try {
+                if (mAction != null) {
+                    mAction.send();
+                } else {
+                    Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    getContext().startActivity(intent);
+                }
+            } catch (CanceledException e) {
+                e.printStackTrace();
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Looks at the slice and determines which items are best to use to compose the shortcut.
+     */
+    private void determineShortcutItems(Context context, Slice slice) {
+        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(slice, 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(slice, SliceItem.TYPE_IMAGE, Slice.HINT_TITLE,
+                    null);
+        }
+        if (mLabel == null) {
+            mLabel = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+                    null);
+        }
+        // Second fallback: first image and text
+        if (mIcon == null) {
+            mIcon = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, (String) null,
+                    null);
+        }
+        if (mLabel == null) {
+            mLabel = SliceQuery.find(slice, 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) {
+                    Slice.Builder sb = new Slice.Builder(slice.getUri());
+                    Drawable icon = pm.getApplicationIcon(appInfo);
+                    sb.addIcon(SliceViewUtil.createIconFromDrawable(icon), Slice.HINT_LARGE);
+                    mIcon = sb.build().getItems().get(0);
+                }
+                if (mLabel == null) {
+                    Slice.Builder sb = new Slice.Builder(slice.getUri());
+                    sb.addText(pm.getApplicationLabel(appInfo));
+                    mLabel = sb.build().getItems().get(0);
+                }
+                if (mAction == null) {
+                    mAction = PendingIntent.getActivity(context, 0,
+                            pm.getLaunchIntentForPackage(appInfo.packageName), 0);
+                }
+            }
+        }
+    }
+}
diff --git a/androidx/app/slice/widget/SliceLiveData.java b/androidx/app/slice/widget/SliceLiveData.java
new file mode 100644
index 0000000..eaaef50
--- /dev/null
+++ b/androidx/app/slice/widget/SliceLiveData.java
@@ -0,0 +1,76 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.arch.lifecycle.LiveData;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+
+/**
+ * Class with factory methods for creating LiveData that observes slices.
+ *
+ * @see #fromUri(Context, Uri)
+ * @see LiveData
+ */
+public final class SliceLiveData {
+
+    /**
+     * Produces an {@link LiveData} that tracks a Slice for a given Uri. To use
+     * this method your app must have the permission to the slice Uri or hold
+     * {@link android.Manifest.permission#BIND_SLICE}).
+     */
+    public static LiveData<Slice> fromUri(Context context, Uri uri) {
+        return new SliceLiveDataImpl(context.getApplicationContext(), uri);
+    }
+
+    private static class SliceLiveDataImpl extends LiveData<Slice> {
+        private final Uri mUri;
+        private final Context mContext;
+
+        private SliceLiveDataImpl(Context context, Uri uri) {
+            super();
+            mContext = context;
+            mUri = uri;
+            // TODO: Check if uri points at a Slice?
+        }
+
+        @Override
+        protected void onActive() {
+            AsyncTask.execute(this::updateSlice);
+            mContext.getContentResolver().registerContentObserver(mUri, false, mObserver);
+        }
+
+        @Override
+        protected void onInactive() {
+            mContext.getContentResolver().unregisterContentObserver(mObserver);
+        }
+
+        private void updateSlice() {
+            postValue(Slice.bindSlice(mContext.getContentResolver(), mUri));
+        }
+
+        private final ContentObserver mObserver = new ContentObserver(new Handler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                AsyncTask.execute(SliceLiveDataImpl.this::updateSlice);
+            }
+        };
+    }
+}
diff --git a/androidx/app/slice/widget/SliceView.java b/androidx/app/slice/widget/SliceView.java
new file mode 100644
index 0000000..6e12dba
--- /dev/null
+++ b/androidx/app/slice/widget/SliceView.java
@@ -0,0 +1,312 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.arch.lifecycle.Observer;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import java.util.List;
+
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
+ * able to present slice content in a templated format outside of the associated app. The way this
+ * content is displayed depends on the structure of the slice, the hints associated with the
+ * content, and the mode that SliceView is configured for. The modes that SliceView supports are:
+ * <ul>
+ * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main
+ * content or action associated with the slice.</li>
+ * <li><b>Small</b>: The small format has a restricted height and can present a single
+ * {@link SliceItem} or a limited collection of items.</li>
+ * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is
+ * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can
+ * comfortably fit.</li>
+ * </ul>
+ * <p>
+ * When constructing a slice, the contents of it can be annotated with hints, these provide the OS
+ * with some information on how the content should be displayed. For example, text annotated with
+ * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
+ * with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
+ * <p>
+ * Example usage:
+ *
+ * <pre class="prettyprint">
+ * SliceView v = new SliceView(getContext());
+ * v.setMode(desiredMode);
+ * LiveData<Slice> liveData = SliceLiveData.fromUri(sliceUri);
+ * liveData.observe(lifecycleOwner, v);
+ * </pre>
+ * @see SliceLiveData
+ */
+public class SliceView extends ViewGroup implements Observer<Slice> {
+
+    private static final String TAG = "SliceView";
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public abstract static class SliceModeView extends FrameLayout {
+
+        public SliceModeView(Context context) {
+            super(context);
+        }
+
+        /**
+         * @return the mode of the slice being presented.
+         */
+        public abstract int getMode();
+
+        /**
+         * @param slice the slice to show in this view.
+         */
+        public abstract void setSlice(Slice slice);
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+            MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+    })
+    public @interface SliceMode {}
+
+    /**
+     * Mode indicating this slice should be presented in small template format.
+     */
+    public static final int MODE_SMALL       = 1;
+    /**
+     * Mode indicating this slice should be presented in large template format.
+     */
+    public static final int MODE_LARGE       = 2;
+    /**
+     * 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 int MODE_SHORTCUT    = 3;
+
+    /**
+     * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+     * that selection.
+     */
+    private static final int MODE_AUTO = 0;
+
+    private int mMode = MODE_AUTO;
+    private SliceModeView mCurrentView;
+    private final ActionRow mActions;
+    private Slice mCurrentSlice;
+    private boolean mShowActions = true;
+    private boolean mIsScrollable;
+    private final int mShortcutSize;
+
+    public SliceView(Context context) {
+        this(context, null);
+    }
+
+    public SliceView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mActions = new ActionRow(getContext(), true);
+        mActions.setBackground(new ColorDrawable(0xffeeeeee));
+        mCurrentView = new LargeTemplateView(getContext());
+        addView(mCurrentView, getChildLp(mCurrentView));
+        addView(mActions, getChildLp(mActions));
+        mShortcutSize = getContext().getResources()
+                .getDimensionPixelSize(R.dimen.abc_slice_shortcut_size);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int mode = MeasureSpec.getMode(widthMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        if (MODE_SHORTCUT == mMode) {
+            width = mShortcutSize;
+        }
+        if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.UNSPECIFIED) {
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+        }
+        measureChildren(widthMeasureSpec, heightMeasureSpec);
+        int actionHeight = mActions.getVisibility() != View.GONE
+                ? mActions.getMeasuredHeight()
+                : 0;
+        int newHeightSpec = MeasureSpec.makeMeasureSpec(
+                mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
+        setMeasuredDimension(width, newHeightSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        mCurrentView.layout(0, 0, mCurrentView.getMeasuredWidth(),
+                mCurrentView.getMeasuredHeight());
+        if (mActions.getVisibility() != View.GONE) {
+            mActions.layout(0, mCurrentView.getMeasuredHeight(), mActions.getMeasuredWidth(),
+                    mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
+        }
+    }
+
+    @Override
+    public void onChanged(@Nullable Slice slice) {
+        setSlice(slice);
+    }
+
+    /**
+     * Populates this view to the provided {@link Slice}.
+     *
+     * This will not update automatically if the slice content changes, for live
+     * content see {@link SliceLiveData}.
+     */
+    public void setSlice(@Nullable Slice slice) {
+        mCurrentSlice = slice;
+        reinflate();
+    }
+
+    /**
+     * Set the mode this view should present in.
+     */
+    public void setMode(@SliceMode int mode) {
+        setMode(mode, false /* animate */);
+    }
+
+    /**
+     * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}.
+     */
+    public void setScrollable(boolean isScrollable) {
+        mIsScrollable = isScrollable;
+        reinflate();
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setMode(@SliceMode int mode, boolean animate) {
+        if (animate) {
+            Log.e(TAG, "Animation not supported yet");
+        }
+        mMode = mode;
+        reinflate();
+    }
+
+    /**
+     * @return the mode this view is presenting in.
+     */
+    public @SliceMode int getMode() {
+        if (mMode == MODE_AUTO) {
+            return MODE_LARGE;
+        }
+        return mMode;
+    }
+
+    /**
+     * @hide
+     *
+     * Whether this view should show a row of actions with it.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public void setShowActionRow(boolean show) {
+        mShowActions = show;
+        reinflate();
+    }
+
+    private SliceModeView createView(int mode) {
+        switch (mode) {
+            case MODE_SHORTCUT:
+                return new ShortcutView(getContext());
+            case MODE_SMALL:
+                return new SmallTemplateView(getContext());
+        }
+        return new LargeTemplateView(getContext());
+    }
+
+    private void reinflate() {
+        if (mCurrentSlice == null) {
+            return;
+        }
+        // TODO: Smarter mapping here from one state to the next.
+        SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
+        List<SliceItem> items = mCurrentSlice.getItems();
+        SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
+                Slice.HINT_ACTIONS,
+                null);
+        int mode = getMode();
+        if (mode != mCurrentView.getMode()) {
+            removeAllViews();
+            mCurrentView = createView(mode);
+            addView(mCurrentView, getChildLp(mCurrentView));
+            addView(mActions, getChildLp(mActions));
+        }
+        if (mode == MODE_LARGE) {
+            ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable);
+        }
+        if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
+            mCurrentView.setVisibility(View.VISIBLE);
+            mCurrentView.setSlice(mCurrentSlice);
+        } else {
+            mCurrentView.setVisibility(View.GONE);
+        }
+
+        boolean showActions = mShowActions && actionRow != null
+                && mode != MODE_SHORTCUT;
+        if (showActions) {
+            mActions.setActions(actionRow, color);
+            mActions.setVisibility(View.VISIBLE);
+        } else {
+            mActions.setVisibility(View.GONE);
+        }
+    }
+
+    private LayoutParams getChildLp(View child) {
+        if (child instanceof ShortcutView) {
+            return new LayoutParams(mShortcutSize, mShortcutSize);
+        } else {
+            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        }
+    }
+
+    private static void validate(Uri sliceUri) {
+        if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+        if (sliceUri.getPathSegments().size() == 0) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+    }
+}
diff --git a/androidx/app/slice/widget/SliceViewUtil.java b/androidx/app/slice/widget/SliceViewUtil.java
new file mode 100644
index 0000000..62844c1
--- /dev/null
+++ b/androidx/app/slice/widget/SliceViewUtil.java
@@ -0,0 +1,199 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.support.annotation.AttrRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+import android.text.format.DateUtils;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.Calendar;
+
+/**
+ * A bunch of utilities for slice UI.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SliceViewUtil {
+
+    /**
+     */
+    @ColorInt
+    public static int getColorAccent(@NonNull Context context) {
+        return getColorAttr(context, android.R.attr.colorAccent);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getColorError(@NonNull Context context) {
+        return getColorAttr(context, android.R.attr.colorError);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getDefaultColor(@NonNull Context context, int resId) {
+        final ColorStateList list = context.getResources().getColorStateList(resId,
+                context.getTheme());
+
+        return list.getDefaultColor();
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getDisabled(@NonNull Context context, int inputColor) {
+        return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int applyAlphaAttr(@NonNull Context context, @AttrRes int attr, int inputColor) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        float alpha = ta.getFloat(0, 0);
+        ta.recycle();
+        return applyAlpha(alpha, inputColor);
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int applyAlpha(float alpha, int inputColor) {
+        alpha *= Color.alpha(inputColor);
+        return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+                Color.blue(inputColor));
+    }
+
+    /**
+     */
+    @ColorInt
+    public static int getColorAttr(@NonNull Context context, @AttrRes int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        @ColorInt int colorAccent = ta.getColor(0, 0);
+        ta.recycle();
+        return colorAccent;
+    }
+
+    /**
+     */
+    public static int getThemeAttr(@NonNull Context context, @AttrRes int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        int theme = ta.getResourceId(0, 0);
+        ta.recycle();
+        return theme;
+    }
+
+    /**
+     */
+    public static Drawable getDrawable(@NonNull Context context, @AttrRes int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        Drawable drawable = ta.getDrawable(0);
+        ta.recycle();
+        return drawable;
+    }
+
+    /**
+     */
+    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);
+    }
+
+    /**
+     */
+    public static void createCircledIcon(@NonNull Context context, int color, int iconSizePx,
+            Icon icon, boolean isLarge, ViewGroup parent) {
+        ImageView v = new ImageView(context);
+        v.setImageIcon(icon);
+        parent.addView(v);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        if (isLarge) {
+            // XXX better way to convert from icon -> bitmap or crop an icon (?)
+            Bitmap iconBm = Bitmap.createBitmap(iconSizePx, iconSizePx, Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            v.layout(0, 0, iconSizePx, iconSizePx);
+            v.draw(iconCanvas);
+            v.setImageBitmap(getCircularBitmap(iconBm));
+        } else {
+            v.setColorFilter(Color.WHITE);
+        }
+        lp.width = iconSizePx;
+        lp.height = iconSizePx;
+        lp.gravity = Gravity.CENTER;
+    }
+
+    /**
+     */
+    public static @NonNull Bitmap getCircularBitmap(Bitmap bitmap) {
+        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
+                bitmap.getHeight(), Config.ARGB_8888);
+        Canvas canvas = new Canvas(output);
+        final Paint paint = new Paint();
+        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        paint.setAntiAlias(true);
+        canvas.drawARGB(0, 0, 0, 0);
+        canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
+                bitmap.getWidth() / 2, paint);
+        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+        canvas.drawBitmap(bitmap, rect, rect, paint);
+        return output;
+    }
+
+    /**
+     */
+    public static CharSequence getRelativeTimeString(long time) {
+        return DateUtils.getRelativeTimeSpanString(time, Calendar.getInstance().getTimeInMillis(),
+                DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
+    }
+}
diff --git a/androidx/app/slice/widget/SmallTemplateView.java b/androidx/app/slice/widget/SmallTemplateView.java
new file mode 100644
index 0000000..f430602
--- /dev/null
+++ b/androidx/app/slice/widget/SmallTemplateView.java
@@ -0,0 +1,367 @@
+/*
+ * 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 androidx.app.slice.widget;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.support.annotation.RestrictTo;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.app.slice.builders.SliceHints;
+import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.view.R;
+
+/**
+ * Small template is also used to construct list items for use with {@link LargeTemplateView}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class SmallTemplateView extends SliceView.SliceModeView implements
+        LargeSliceAdapter.SliceListView, View.OnClickListener {
+
+    private static final String TAG = "SmallTemplateView";
+
+    // The number of items that fit on the right hand side of a small slice
+    private static final int MAX_END_ITEMS = 3;
+
+    private SliceItem mColorItem;
+
+    private int mIconSize;
+    private int mPadding;
+
+    private LinearLayout mStartContainer;
+    private LinearLayout mContent;
+    private TextView mPrimaryText;
+    private TextView mSecondaryText;
+    private LinearLayout mEndContainer;
+
+    private SliceItem mRowAction;
+    private View mDivider;
+    private Switch mToggle;
+
+    public SmallTemplateView(Context context) {
+        super(context);
+        mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_icon_size);
+        mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.abc_slice_padding);
+        inflate(context, R.layout.abc_slice_small_template, this);
+
+        mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
+        mContent = (LinearLayout) findViewById(android.R.id.content);
+        mPrimaryText = (TextView) findViewById(android.R.id.title);
+        mSecondaryText = (TextView) findViewById(android.R.id.summary);
+        mDivider = findViewById(R.id.divider);
+        mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
+    }
+
+    @Override
+    public @SliceView.SliceMode int getMode() {
+        return SliceView.MODE_SMALL;
+    }
+
+    @Override
+    public void setColor(SliceItem color) {
+        mColorItem = color;
+    }
+
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        populateViews(slice, slice);
+    }
+    @Override
+    public void setSlice(Slice slice) {
+        Slice.Builder sb = new Slice.Builder(slice.getUri());
+        sb.addSubSlice(slice);
+        Slice parentSlice = sb.build();
+        populateViews(parentSlice.getItems().get(0), getFirstSlice(slice));
+    }
+
+    private SliceItem getFirstSlice(Slice slice) {
+        List<SliceItem> items = slice.getItems();
+        if (items.size() > 0 && items.get(0).getType() == SliceItem.TYPE_SLICE) {
+            // Check if this slice is appropriate to use to populate small template
+            SliceItem firstSlice = items.get(0);
+            if (firstSlice.hasHint(Slice.HINT_LIST)) {
+                // Check for header, use that if it exists
+                SliceItem header = SliceQuery.find(firstSlice, SliceItem.TYPE_SLICE,
+                        null,
+                        new String[] {
+                                Slice.HINT_LIST_ITEM, Slice.HINT_LIST
+                        });
+                if (header != null) {
+                    return SliceQuery.findFirstSlice(header);
+                } else {
+                    // Otherwise use the first list item
+                    SliceItem newFirst = firstSlice.getSlice().getItems().get(0);
+                    return SliceQuery.findFirstSlice(newFirst);
+                }
+            } else {
+                // Not a list, find first slice with non-slice children
+                return SliceQuery.findFirstSlice(firstSlice);
+            }
+        }
+        // Get it as a SliceItem type slice
+        Slice.Builder sb = new Slice.Builder(slice.getUri());
+        Slice s = sb.addSubSlice(slice).build();
+        return s.getItems().get(0);
+    }
+
+    private void populateViews(SliceItem fullSlice, SliceItem sliceItem) {
+        resetViews();
+        ArrayList<SliceItem> items = new ArrayList<>();
+        if (sliceItem.getType() == SliceItem.TYPE_SLICE) {
+            items = new ArrayList<>(sliceItem.getSlice().getItems());
+        } else {
+            items.add(sliceItem);
+        }
+
+        // These are the things that can go in our small template
+        SliceItem startItem = null;
+        SliceItem titleItem = null;
+        SliceItem subTitle = null;
+        ArrayList<SliceItem> endItems = new ArrayList<>();
+
+        // If the first item is an action let's check if it should be used to populate the content
+        // or if it should be in the start position.
+        SliceItem firstSlice = items.size() > 0 ? items.get(0) : null;
+        if (firstSlice != null && firstSlice.getType() == SliceItem.TYPE_ACTION) {
+            if (!SliceQuery.isSimpleAction(firstSlice)) {
+                mRowAction = firstSlice;
+                items.remove(0);
+                // Populating with first action, bias to use slice associated with this action
+                items.addAll(0, mRowAction.getSlice().getItems());
+            } else {
+                // It's simple so maybe it's a start item
+                startItem = items.get(0);
+            }
+        }
+
+        // Look through our items and try to figure out main content
+        for (int i = 0; i < items.size(); i++) {
+            SliceItem item = items.get(i);
+            List<String> hints = item.getHints();
+            int itemType = item.getType();
+            if (hints.contains(Slice.HINT_TITLE)) {
+                // Things with these hints could go in the title / start position
+                if ((startItem == null || !startItem.hasHint(Slice.HINT_TITLE))
+                        && SliceQuery.isStartType(item)) {
+                    startItem = item;
+                } else if ((titleItem == null || !titleItem.hasHint(Slice.HINT_TITLE))
+                        && itemType == SliceItem.TYPE_TEXT) {
+                    titleItem = item;
+                } else {
+                    endItems.add(item);
+                }
+            } else if (item.getType() == SliceItem.TYPE_TEXT) {
+                if (titleItem == null) {
+                    titleItem = item;
+                } else if (subTitle == null) {
+                    subTitle = item;
+                } else {
+                    endItems.add(item);
+                }
+            } else if (item.getType() == SliceItem.TYPE_SLICE) {
+                List<SliceItem> subItems = item.getSlice().getItems();
+                for (int j = 0; j < subItems.size(); j++) {
+                    endItems.add(subItems.get(j));
+                }
+            } else {
+                endItems.add(item);
+            }
+        }
+
+        // Populate main part of the template
+        if (startItem != null) {
+            // TODO - check for icon, timestamp, action with icon
+        }
+        if (titleItem != null) {
+            mPrimaryText.setText(titleItem.getText());
+        }
+        mPrimaryText.setVisibility(titleItem != null ? View.VISIBLE : View.GONE);
+        if (subTitle != null) {
+            mSecondaryText.setText(subTitle.getText());
+        }
+        mSecondaryText.setVisibility(subTitle != null ? View.VISIBLE : View.GONE);
+
+        // Figure out what end items we're showing
+        // If we're showing an action in this row check if it's a toggle
+        if (mRowAction != null && SliceQuery.hasHints(mRowAction.getSlice(), SliceHints.HINT_TOGGLE)
+                && addToggle(mRowAction)) {
+            // Can't show more end actions if we have a toggle so we're done
+            makeClickable(this);
+            return;
+        }
+        // Check if we have a toggle somewhere in our end items
+        SliceItem toggleItem = endItems.stream()
+                .filter(item -> (item.getType() == SliceItem.TYPE_ACTION
+                        && SliceQuery.hasHints(item.getSlice(), SliceHints.HINT_TOGGLE)))
+                .findFirst().orElse(null);
+        if (toggleItem != null) {
+            if (addToggle(toggleItem)) {
+                mDivider.setVisibility(mRowAction != null ? View.VISIBLE : View.GONE);
+                makeClickable(mRowAction != null ? mContent : this);
+                // Can't show more end actions if we have a toggle so we're done
+                return;
+            }
+        }
+        // If we're here we can still show end items
+        SliceItem colorItem = SliceQuery.find(fullSlice, SliceItem.TYPE_COLOR);
+        int color = colorItem != null
+                ? colorItem.getColor()
+                : (mColorItem != null)
+                        ? mColorItem.getColor()
+                        : -1;
+        boolean clickableEndItem = false;
+        int itemCount = 0;
+        for (int i = 0; i < items.size(); i++) {
+            SliceItem item = items.get(i);
+            if (itemCount <= MAX_END_ITEMS) {
+                if (item.getType() == SliceItem.TYPE_ACTION) {
+                    if (SliceQuery.hasHints(item.getSlice(), SliceHints.HINT_TOGGLE)) {
+                        if (addToggle(item)) {
+                            break;
+                        }
+                    }
+                    if (addIcon(item, color, mEndContainer)) {
+                        clickableEndItem = true;
+                        itemCount++;
+                    }
+                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+                    addIcon(item, color, mEndContainer);
+                    itemCount++;
+                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+                    TextView tv = new TextView(getContext());
+                    tv.setText(SliceViewUtil.getRelativeTimeString(item.getTimestamp()));
+                    mEndContainer.addView(tv);
+                    itemCount++;
+                }
+            }
+        }
+        if (mRowAction != null) {
+            makeClickable(clickableEndItem ? mContent : this);
+        }
+    }
+
+    /**
+     * @return Whether a toggle was added.
+     */
+    private boolean addToggle(SliceItem toggleItem) {
+        if (toggleItem.getType() != SliceItem.TYPE_ACTION
+                || !SliceQuery.hasHints(toggleItem.getSlice(), SliceHints.HINT_TOGGLE)) {
+            return false;
+        }
+        mToggle = new Switch(getContext());
+        mEndContainer.addView(mToggle);
+        mToggle.setChecked(SliceQuery.hasHints(toggleItem.getSlice(), Slice.HINT_SELECTED));
+        mToggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            try {
+                PendingIntent pi = toggleItem.getAction();
+                Intent i = new Intent().putExtra(SliceHints.EXTRA_TOGGLE_STATE, isChecked);
+                pi.send(getContext(), 0, i, null, null);
+            } catch (CanceledException e) {
+                mToggle.setSelected(!isChecked);
+            }
+        });
+        return true;
+    }
+
+    /**
+     * @return Whether an icon was added.
+     */
+    private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
+        SliceItem image = null;
+        SliceItem action = null;
+        if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
+            image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
+            action = sliceItem;
+        } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
+            image = sliceItem;
+        }
+        if (image != null) {
+            ImageView iv = new ImageView(getContext());
+            iv.setImageIcon(image.getIcon());
+            if (action != null) {
+                final SliceItem sliceAction = action;
+                iv.setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                sliceAction.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+                iv.setBackground(SliceViewUtil.getDrawable(getContext(),
+                        android.R.attr.selectableItemBackground));
+            }
+            if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
+                iv.setColorFilter(color);
+            }
+            container.addView(iv);
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
+            lp.width = mIconSize;
+            lp.height = mIconSize;
+            lp.setMarginStart(mPadding);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (mRowAction != null && mRowAction.getType() == SliceItem.TYPE_ACTION) {
+            if (mToggle != null
+                    && SliceQuery.hasHints(mRowAction.getSlice(), SliceHints.HINT_TOGGLE)) {
+                mToggle.toggle();
+                return;
+            }
+            AsyncTask.execute(() -> {
+                try {
+                    mRowAction.getAction().send();
+                } catch (CanceledException e) {
+                    Log.w(TAG, "PendingIntent for slice cannot be sent", e);
+                }
+            });
+        }
+    }
+
+    private void makeClickable(View layout) {
+        layout.setOnClickListener(this);
+        layout.setBackground(SliceViewUtil.getDrawable(getContext(),
+                android.R.attr.selectableItemBackground));
+    }
+
+    private void resetViews() {
+        mStartContainer.removeAllViews();
+        mEndContainer.removeAllViews();
+        mPrimaryText.setText(null);
+        mSecondaryText.setText(null);
+    }
+}
diff --git a/androidx/recyclerview/selection/ActivationCallbacks.java b/androidx/recyclerview/selection/ActivationCallbacks.java
new file mode 100644
index 0000000..606f35a
--- /dev/null
+++ b/androidx/recyclerview/selection/ActivationCallbacks.java
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class ActivationCallbacks<K> {
+
+    static <K> ActivationCallbacks<K> dummy() {
+        return new ActivationCallbacks<K>() {
+            @Override
+            public boolean onItemActivated(ItemDetails item, MotionEvent e) {
+                return false;
+            }
+        };
+    }
+
+    /**
+     * Called when an item is activated. An item is activitated, for example, when
+     * there is no active selection and the user double clicks an item with a
+     * pointing device like a Mouse.
+     *
+     * @param item details of the item.
+     * @param e the event associated with item.
+     * @return true if the event was handled.
+     */
+    public abstract boolean onItemActivated(ItemDetails<K> item, MotionEvent e);
+}
diff --git a/androidx/recyclerview/selection/AutoScroller.java b/androidx/recyclerview/selection/AutoScroller.java
new file mode 100644
index 0000000..13e87bd
--- /dev/null
+++ b/androidx/recyclerview/selection/AutoScroller.java
@@ -0,0 +1,42 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.graphics.Point;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Provides support for auto-scrolling a view.
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class AutoScroller {
+
+    /**
+     * Resets state of the scroller. Call this when the user activity that is driving
+     * auto-scrolling is done.
+     */
+    protected abstract void reset();
+
+    /**
+     * Processes a new input location.
+     * @param location
+     */
+    protected abstract void scroll(Point location);
+}
diff --git a/androidx/recyclerview/selection/BandPredicate.java b/androidx/recyclerview/selection/BandPredicate.java
new file mode 100644
index 0000000..9a5ae47
--- /dev/null
+++ b/androidx/recyclerview/selection/BandPredicate.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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Provides a means of controlling when and where band selection can be initiated.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class BandPredicate {
+
+    /** @return true if band selection can be initiated in response to the {@link MotionEvent}. */
+    public abstract boolean canInitiate(MotionEvent e);
+
+    private static boolean hasSupportedLayoutManager(RecyclerView recView) {
+        RecyclerView.LayoutManager lm = recView.getLayoutManager();
+        return lm instanceof GridLayoutManager
+                || lm instanceof LinearLayoutManager;
+    }
+
+    /**
+     * Creates a new band predicate that permits initiation of band on areas
+     * of a RecyclerView that map to RecyclerView.NO_POSITION.
+     *
+     * @param recView
+     * @return
+     */
+    @SuppressWarnings("unused")
+    public static BandPredicate noPosition(RecyclerView recView) {
+        return new NoPosition(recView);
+    }
+
+    /**
+     * Creates a new band predicate that permits initiation of band
+     * anywhere doesn't correspond to a draggable region of a item.
+     *
+     * @param detailsLookup
+     * @return
+     */
+    public static BandPredicate notDraggable(
+            RecyclerView recView, ItemDetailsLookup detailsLookup) {
+        return new NotDraggable(recView, detailsLookup);
+    }
+
+    /**
+     * A BandPredicate that allows initiation of band selection only in areas of RecyclerView
+     * that have {@link RecyclerView#NO_POSITION}. In most cases, this will be the empty areas
+     * between views.
+     */
+    private static final class NoPosition extends BandPredicate {
+
+        private final RecyclerView mRecView;
+
+        NoPosition(RecyclerView recView) {
+            checkArgument(recView != null);
+
+            mRecView = recView;
+        }
+
+        @Override
+        public boolean canInitiate(MotionEvent e) {
+            if (!hasSupportedLayoutManager(mRecView)
+                    || mRecView.hasPendingAdapterUpdates()) {
+                return false;
+            }
+
+            View itemView = mRecView.findChildViewUnder(e.getX(), e.getY());
+            int position = itemView != null
+                    ? mRecView.getChildAdapterPosition(itemView)
+                    : RecyclerView.NO_POSITION;
+
+            return position == RecyclerView.NO_POSITION;
+        }
+    }
+
+    /**
+     * A BandPredicate that allows initiation of band selection in any area that is not
+     * draggable as determined by consulting
+     * {@link ItemDetailsLookup#inItemDragRegion(MotionEvent)}.
+     */
+    private static final class NotDraggable extends BandPredicate {
+
+        private final RecyclerView mRecView;
+        private final ItemDetailsLookup mDetailsLookup;
+
+        NotDraggable(RecyclerView recView, ItemDetailsLookup detailsLookup) {
+            checkArgument(recView != null);
+            checkArgument(detailsLookup != null);
+
+            mRecView = recView;
+            mDetailsLookup = detailsLookup;
+        }
+
+        @Override
+        public boolean canInitiate(MotionEvent e) {
+            if (!hasSupportedLayoutManager(mRecView)
+                    || mRecView.hasPendingAdapterUpdates()) {
+                return false;
+            }
+
+            @Nullable ItemDetailsLookup.ItemDetails details = mDetailsLookup.getItemDetails(e);
+            return (details == null) || !details.inDragRegion(e);
+        }
+    }
+}
diff --git a/androidx/recyclerview/selection/BandSelectionHelper.java b/androidx/recyclerview/selection/BandSelectionHelper.java
new file mode 100644
index 0000000..5362e2b
--- /dev/null
+++ b/androidx/recyclerview/selection/BandSelectionHelper.java
@@ -0,0 +1,355 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import java.util.Set;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Provides mouse driven band-selection support when used in conjunction with a {@link RecyclerView}
+ * instance. This class is responsible for rendering a band overlay and manipulating selection
+ * status of the items it intersects with.
+ *
+ * <p> Given the recycling nature of RecyclerView items that have scrolled off-screen would not
+ * be selectable with a band that itself was partially rendered off-screen. To address this,
+ * BandSelectionController builds a model of the list/grid information presented by RecyclerView as
+ * the user interacts with items using their pointer (and the band). Selectable items that intersect
+ * with the band, both on and off screen, are selected on pointer up.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ */
+class BandSelectionHelper<K> implements OnItemTouchListener {
+
+    static final String TAG = "BandSelectionHelper";
+    static final boolean DEBUG = false;
+
+    private final BandHost mHost;
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final SelectionHelper<K> mSelectionHelper;
+    private final SelectionPredicate<K> mSelectionPredicate;
+    private final BandPredicate mBandPredicate;
+    private final FocusCallbacks<K> mFocusCallbacks;
+    private final ContentLock mLock;
+    private final AutoScroller mScroller;
+    private final GridModel.SelectionObserver mGridObserver;
+
+    private @Nullable Point mCurrentPosition;
+    private @Nullable Point mOrigin;
+    private @Nullable GridModel mModel;
+
+    /**
+     * See {@link BandSelectionHelper#create}.
+     */
+    BandSelectionHelper(
+            BandHost host,
+            AutoScroller scroller,
+            ItemKeyProvider<K> keyProvider,
+            SelectionHelper<K> selectionHelper,
+            SelectionPredicate<K> selectionPredicate,
+            BandPredicate bandPredicate,
+            FocusCallbacks<K> focusCallbacks,
+            ContentLock lock) {
+
+        checkArgument(host != null);
+        checkArgument(scroller != null);
+        checkArgument(keyProvider != null);
+        checkArgument(selectionHelper != null);
+        checkArgument(selectionPredicate != null);
+        checkArgument(bandPredicate != null);
+        checkArgument(focusCallbacks != null);
+        checkArgument(lock != null);
+
+        mHost = host;
+        mKeyProvider = keyProvider;
+        mSelectionHelper = selectionHelper;
+        mSelectionPredicate = selectionPredicate;
+        mBandPredicate = bandPredicate;
+        mFocusCallbacks = focusCallbacks;
+        mLock = lock;
+
+        mHost.addOnScrollListener(
+                new OnScrollListener() {
+                    @Override
+                    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                        BandSelectionHelper.this.onScrolled(recyclerView, dx, dy);
+                    }
+                });
+
+        mScroller = scroller;
+
+        mGridObserver = new GridModel.SelectionObserver<K>() {
+            @Override
+            public void onSelectionChanged(Set<K> updatedSelection) {
+                mSelectionHelper.setProvisionalSelection(updatedSelection);
+            }
+        };
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @return new BandSelectionHelper instance.
+     */
+    static <K> BandSelectionHelper create(
+            RecyclerView recView,
+            AutoScroller scroller,
+            @DrawableRes int bandOverlayId,
+            ItemKeyProvider<K> keyProvider,
+            SelectionHelper<K> selectionHelper,
+            SelectionPredicate<K> selectionPredicate,
+            BandPredicate bandPredicate,
+            FocusCallbacks<K> focusCallbacks,
+            ContentLock lock) {
+
+        return new BandSelectionHelper<>(
+                new DefaultBandHost<>(recView, bandOverlayId, keyProvider, selectionPredicate),
+                scroller,
+                keyProvider,
+                selectionHelper,
+                selectionPredicate,
+                bandPredicate,
+                focusCallbacks,
+                lock);
+    }
+
+    @VisibleForTesting
+    boolean isActive() {
+        boolean active = mModel != null;
+        if (DEBUG && active) {
+            mLock.checkLocked();
+        }
+        return active;
+    }
+
+    /**
+     * Clients must call reset when there are any material changes to the layout of items
+     * in RecyclerView.
+     */
+    void reset() {
+        if (!isActive()) {
+            return;
+        }
+
+        mHost.hideBand();
+        if (mModel != null) {
+            mModel.stopCapturing();
+            mModel.onDestroy();
+        }
+
+        mModel = null;
+        mOrigin = null;
+
+        mScroller.reset();
+        mLock.unblock();
+    }
+
+    @VisibleForTesting
+    boolean shouldStart(MotionEvent e) {
+        // b/30146357 && b/23793622. onInterceptTouchEvent does not dispatch events to onTouchEvent
+        // unless the event is != ACTION_DOWN. Thus, we need to actually start band selection when
+        // mouse moves.
+        return MotionEvents.isPrimaryButtonPressed(e)
+                && MotionEvents.isActionMove(e)
+                && mBandPredicate.canInitiate(e)
+                && !isActive();
+    }
+
+    @VisibleForTesting
+    boolean shouldStop(MotionEvent e) {
+        return isActive()
+                && (MotionEvents.isActionUp(e)
+                || MotionEvents.isActionPointerUp(e)
+                || MotionEvents.isActionCancel(e));
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(RecyclerView unused, MotionEvent e) {
+        if (shouldStart(e)) {
+            startBandSelect(e);
+        } else if (shouldStop(e)) {
+            endBandSelect();
+        }
+
+        return isActive();
+    }
+
+    /**
+     * Processes a MotionEvent by starting, ending, or resizing the band select overlay.
+     */
+    @Override
+    public void onTouchEvent(RecyclerView unused, MotionEvent e) {
+        if (shouldStop(e)) {
+            endBandSelect();
+            return;
+        }
+
+        // We shouldn't get any events in this method when band select is not active,
+        // but it turns some guests show up late to the party.
+        // Probably happening when a re-layout is happening to the ReyclerView (ie. Pull-To-Refresh)
+        if (!isActive()) {
+            return;
+        }
+
+        if (DEBUG) {
+            checkArgument(MotionEvents.isActionMove(e));
+            checkState(mModel != null);
+        }
+
+        mCurrentPosition = MotionEvents.getOrigin(e);
+
+        mModel.resizeSelection(mCurrentPosition);
+
+        resizeBand();
+        mScroller.scroll(mCurrentPosition);
+    }
+
+    @Override
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+    }
+
+    /**
+     * Starts band select by adding the drawable to the RecyclerView's overlay.
+     */
+    private void startBandSelect(MotionEvent e) {
+        checkState(!isActive());
+
+        if (!MotionEvents.isCtrlKeyPressed(e)) {
+            mSelectionHelper.clearSelection();
+        }
+
+        Point origin = MotionEvents.getOrigin(e);
+        if (DEBUG) Log.d(TAG, "Starting band select @ " + origin);
+
+        mModel = mHost.createGridModel();
+        mModel.addOnSelectionChangedListener(mGridObserver);
+
+        mLock.block();
+        mFocusCallbacks.clearFocus();
+        mOrigin = origin;
+        // NOTE: Pay heed that resizeBand modifies the y coordinates
+        // in onScrolled. Not sure if model expects this. If not
+        // it should be defending against this.
+        mModel.startCapturing(mOrigin);
+    }
+
+    /**
+     * Resizes the band select rectangle by using the origin and the current pointer position as
+     * two opposite corners of the selection.
+     */
+    private void resizeBand() {
+        Rect bounds = new Rect(Math.min(mOrigin.x, mCurrentPosition.x),
+                Math.min(mOrigin.y, mCurrentPosition.y),
+                Math.max(mOrigin.x, mCurrentPosition.x),
+                Math.max(mOrigin.y, mCurrentPosition.y));
+
+        if (VERBOSE) Log.v(TAG, "Resizing band! " + bounds);
+        mHost.showBand(bounds);
+    }
+
+    /**
+     * Ends band select by removing the overlay.
+     */
+    private void endBandSelect() {
+        if (DEBUG) {
+            Log.d(TAG, "Ending band select.");
+            checkState(mModel != null);
+        }
+
+        // TODO: Currently when a band select operation ends outside
+        // of an item (e.g. in the empty area between items),
+        // getPositionNearestOrigin may return an unselected item.
+        // Since the point of this code is to establish the
+        // anchor point for subsequent range operations (SHIFT+CLICK)
+        // we really want to do a better job figuring out the last
+        // item selected (and nearest to the cursor).
+        int firstSelected = mModel.getPositionNearestOrigin();
+        if (firstSelected != GridModel.NOT_SET
+                && mSelectionHelper.isSelected(mKeyProvider.getKey(firstSelected))) {
+            // Establish the band selection point as range anchor. This
+            // allows touch and keyboard based selection activities
+            // to be based on the band selection anchor point.
+            mSelectionHelper.anchorRange(firstSelected);
+        }
+
+        mSelectionHelper.mergeProvisionalSelection();
+        reset();
+    }
+
+    /**
+     * @see RecyclerView.OnScrollListener
+     */
+    private void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+        if (!isActive()) {
+            return;
+        }
+
+        // Adjust the y-coordinate of the origin the opposite number of pixels so that the
+        // origin remains in the same place relative to the view's items.
+        mOrigin.y -= dy;
+        resizeBand();
+    }
+
+    /**
+     * Provides functionality for BandController. Exists primarily to tests that are
+     * fully isolated from RecyclerView.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     */
+    abstract static class BandHost<K> {
+
+        /**
+         * Returns a new GridModel instance.
+         */
+        abstract GridModel<K> createGridModel();
+
+        /**
+         * Show the band covering the bounds.
+         *
+         * @param bounds The boundaries of the band to show.
+         */
+        abstract void showBand(Rect bounds);
+
+        /**
+         * Hide the band.
+         */
+        abstract void hideBand();
+
+        /**
+         * Add a listener to be notified on scroll events.
+         *
+         * @param listener
+         */
+        abstract void addOnScrollListener(RecyclerView.OnScrollListener listener);
+    }
+}
diff --git a/androidx/recyclerview/selection/ContentLock.java b/androidx/recyclerview/selection/ContentLock.java
new file mode 100644
index 0000000..6891eab
--- /dev/null
+++ b/androidx/recyclerview/selection/ContentLock.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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+
+import android.content.Loader;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.util.Log;
+
+/**
+ * ContentLock provides a mechanism to block content from reloading while selection
+ * activities like gesture and band selection are active. Clients using live data
+ * (data loaded, for example by a {@link Loader}), should route calls to load
+ * content through this lock using {@link ContentLock#runWhenUnlocked(Runnable)}.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class ContentLock {
+
+    private static final String TAG = "ContentLock";
+
+    private int mLocks = 0;
+    private @Nullable Runnable mCallback;
+
+    /**
+     * Increment the block count by 1
+     */
+    @MainThread
+    synchronized void block() {
+        mLocks++;
+        if (DEBUG) Log.v(TAG, "Incremented content lock count to " + mLocks + ".");
+    }
+
+    /**
+     * Decrement the block count by 1; If no other object is trying to block and there exists some
+     * callback, that callback will be run
+     */
+    @MainThread
+    synchronized void unblock() {
+        checkState(mLocks > 0);
+
+        mLocks--;
+        if (DEBUG) Log.v(TAG, "Decremented content lock count to " + mLocks + ".");
+
+        if (mLocks == 0 && mCallback != null) {
+            mCallback.run();
+            mCallback = null;
+        }
+    }
+
+    /**
+     * Attempts to run the given Runnable if not-locked, or else the Runnable is set to be ran next
+     * (replacing any previous set Runnables).
+     */
+    @SuppressWarnings("unused")
+    public synchronized void runWhenUnlocked(Runnable runnable) {
+        if (mLocks == 0) {
+            runnable.run();
+        } else {
+            mCallback = runnable;
+        }
+    }
+
+    /**
+     * Allows other selection code to perform a precondition check asserting the state is locked.
+     */
+    void checkLocked() {
+        checkState(mLocks > 0);
+    }
+
+    /**
+     * Allows other selection code to perform a precondition check asserting the state is unlocked.
+     */
+    void checkUnlocked() {
+        checkState(mLocks == 0);
+    }
+}
diff --git a/androidx/recyclerview/selection/DefaultBandHost.java b/androidx/recyclerview/selection/DefaultBandHost.java
new file mode 100644
index 0000000..f0fd4fe
--- /dev/null
+++ b/androidx/recyclerview/selection/DefaultBandHost.java
@@ -0,0 +1,153 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ItemDecoration;
+import android.view.View;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * RecyclerView backed {@link BandSelectionHelper.BandHost}.
+ */
+final class DefaultBandHost<K> extends GridModel.GridHost<K> {
+
+    private static final Rect NILL_RECT = new Rect(0, 0, 0, 0);
+
+    private final RecyclerView mRecView;
+    private final Drawable mBand;
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final SelectionPredicate<K> mSelectionPredicate;
+
+    DefaultBandHost(
+            RecyclerView recView,
+            @DrawableRes int bandOverlayId,
+            ItemKeyProvider<K> keyProvider,
+            SelectionPredicate<K> selectionPredicate) {
+
+        checkArgument(recView != null);
+
+        mRecView = recView;
+        mBand = mRecView.getContext().getResources().getDrawable(bandOverlayId);
+
+        checkArgument(mBand != null);
+        checkArgument(keyProvider != null);
+        checkArgument(selectionPredicate != null);
+
+        mKeyProvider = keyProvider;
+        mSelectionPredicate = selectionPredicate;
+
+        mRecView.addItemDecoration(
+                new ItemDecoration() {
+                    @Override
+                    public void onDrawOver(
+                            Canvas canvas,
+                            RecyclerView unusedParent,
+                            RecyclerView.State unusedState) {
+                        DefaultBandHost.this.onDrawBand(canvas);
+                    }
+                });
+    }
+
+    @Override
+    GridModel<K> createGridModel() {
+        return new GridModel<>(this, mKeyProvider, mSelectionPredicate);
+    }
+
+    @Override
+    int getAdapterPositionAt(int index) {
+        return mRecView.getChildAdapterPosition(mRecView.getChildAt(index));
+    }
+
+    @Override
+    void addOnScrollListener(RecyclerView.OnScrollListener listener) {
+        mRecView.addOnScrollListener(listener);
+    }
+
+    @Override
+    void removeOnScrollListener(RecyclerView.OnScrollListener listener) {
+        mRecView.removeOnScrollListener(listener);
+    }
+
+    @Override
+    Point createAbsolutePoint(Point relativePoint) {
+        return new Point(relativePoint.x + mRecView.computeHorizontalScrollOffset(),
+                relativePoint.y + mRecView.computeVerticalScrollOffset());
+    }
+
+    @Override
+    Rect getAbsoluteRectForChildViewAt(int index) {
+        final View child = mRecView.getChildAt(index);
+        final Rect childRect = new Rect();
+        child.getHitRect(childRect);
+        childRect.left += mRecView.computeHorizontalScrollOffset();
+        childRect.right += mRecView.computeHorizontalScrollOffset();
+        childRect.top += mRecView.computeVerticalScrollOffset();
+        childRect.bottom += mRecView.computeVerticalScrollOffset();
+        return childRect;
+    }
+
+    @Override
+    int getVisibleChildCount() {
+        return mRecView.getChildCount();
+    }
+
+    @Override
+    int getColumnCount() {
+        RecyclerView.LayoutManager layoutManager = mRecView.getLayoutManager();
+        if (layoutManager instanceof GridLayoutManager) {
+            return ((GridLayoutManager) layoutManager).getSpanCount();
+        }
+
+        // Otherwise, it is a list with 1 column.
+        return 1;
+    }
+
+    @Override
+    void showBand(Rect rect) {
+        mBand.setBounds(rect);
+        // TODO: mRecView.invalidateItemDecorations() should work, but it isn't currently.
+        // NOTE: That without invalidating rv, the band only gets updated
+        // when the pointer moves off a the item view into "NO_POSITION" territory.
+        mRecView.invalidate();
+    }
+
+    @Override
+    void hideBand() {
+        mBand.setBounds(NILL_RECT);
+        // TODO: mRecView.invalidateItemDecorations() should work, but it isn't currently.
+        mRecView.invalidate();
+    }
+
+    private void onDrawBand(Canvas c) {
+        mBand.draw(c);
+    }
+
+    @Override
+    boolean hasView(int pos) {
+        return mRecView.findViewHolderForAdapterPosition(pos) != null;
+    }
+}
diff --git a/androidx/recyclerview/selection/DefaultSelectionHelper.java b/androidx/recyclerview/selection/DefaultSelectionHelper.java
new file mode 100644
index 0000000..5625e3d
--- /dev/null
+++ b/androidx/recyclerview/selection/DefaultSelectionHelper.java
@@ -0,0 +1,475 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import androidx.recyclerview.selection.Range.RangeType;
+
+/**
+ * {@link SelectionHelper} providing support for traditional multi-item selection on top
+ * of {@link RecyclerView}.
+ *
+ * <p>The class supports running in a single-select mode, which can be enabled
+ * by passing {@code #MODE_SINGLE} to the constructor.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class DefaultSelectionHelper<K> extends SelectionHelper<K> {
+
+    private static final String TAG = "DefaultSelectionHelper";
+
+    private final Selection<K> mSelection = new Selection<>();
+    private final List<SelectionObserver> mObservers = new ArrayList<>(1);
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final SelectionPredicate<K> mSelectionPredicate;
+    private final RangeCallbacks mRangeCallbacks;
+    private final boolean mSingleSelect;
+
+    private @Nullable Range mRange;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param keyProvider client supplied class providing access to stable ids.
+     * @param selectionPredicate A predicate allowing the client to disallow selection
+     *     of individual elements.
+     */
+    public DefaultSelectionHelper(
+            ItemKeyProvider keyProvider,
+            SelectionPredicate selectionPredicate) {
+
+        checkArgument(keyProvider != null);
+        checkArgument(selectionPredicate != null);
+
+        mKeyProvider = keyProvider;
+        mSelectionPredicate = selectionPredicate;
+        mRangeCallbacks = new RangeCallbacks();
+
+        mSingleSelect = !selectionPredicate.canSelectMultiple();
+    }
+
+    @Override
+    public void addObserver(SelectionObserver callback) {
+        checkArgument(callback != null);
+        mObservers.add(callback);
+    }
+
+    @Override
+    public boolean hasSelection() {
+        return !mSelection.isEmpty();
+    }
+
+    @Override
+    public Selection getSelection() {
+        return mSelection;
+    }
+
+    @Override
+    public void copySelection(Selection dest) {
+        dest.copyFrom(mSelection);
+    }
+
+    @Override
+    public boolean isSelected(@Nullable K key) {
+        return mSelection.contains(key);
+    }
+
+    @Override
+    public void restoreSelection(Selection other) {
+        checkArgument(other != null);
+        setItemsSelectedQuietly(other.mSelection, true);
+        // NOTE: We intentionally don't restore provisional selection. It's provisional.
+        notifySelectionRestored();
+    }
+
+    @Override
+    public boolean setItemsSelected(Iterable<K> keys, boolean selected) {
+        boolean changed = setItemsSelectedQuietly(keys, selected);
+        notifySelectionChanged();
+        return changed;
+    }
+
+    private boolean setItemsSelectedQuietly(Iterable<K> keys, boolean selected) {
+        boolean changed = false;
+        for (K key: keys) {
+            boolean itemChanged = selected
+                    ? canSetState(key, true) && mSelection.add(key)
+                    : canSetState(key, false) && mSelection.remove(key);
+            if (itemChanged) {
+                notifyItemStateChanged(key, selected);
+            }
+            changed |= itemChanged;
+        }
+        return changed;
+    }
+
+    @Override
+    public void clearSelection() {
+        if (!hasSelection()) {
+            return;
+        }
+
+        Selection prev = clearSelectionQuietly();
+        notifySelectionCleared(prev);
+        notifySelectionChanged();
+    }
+
+    @Override
+    public boolean clear() {
+        boolean somethingChanged = hasSelection();
+        clearProvisionalSelection();
+        clearSelection();
+        return somethingChanged;
+    }
+
+    /**
+     * Clears the selection, without notifying selection listeners.
+     * Returns items in previous selection. Callers are responsible for notifying
+     * listeners about changes.
+     */
+    private Selection clearSelectionQuietly() {
+        mRange = null;
+
+        Selection prevSelection = new Selection();
+        if (hasSelection()) {
+            copySelection(prevSelection);
+            mSelection.clear();
+        }
+
+        return prevSelection;
+    }
+
+    @Override
+    public boolean select(K key) {
+        checkArgument(key != null);
+
+        if (!mSelection.contains(key)) {
+            if (!canSetState(key, true)) {
+                if (DEBUG) Log.d(TAG, "Select cancelled by selection predicate test.");
+                return false;
+            }
+
+            // Enforce single selection policy.
+            if (mSingleSelect && hasSelection()) {
+                Selection prev = clearSelectionQuietly();
+                notifySelectionCleared(prev);
+            }
+
+            mSelection.add(key);
+            notifyItemStateChanged(key, true);
+            notifySelectionChanged();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean deselect(K key) {
+        checkArgument(key != null);
+
+        if (mSelection.contains(key)) {
+            if (!canSetState(key, false)) {
+                if (DEBUG) Log.d(TAG, "Deselect cancelled by selection predicate test.");
+                return false;
+            }
+            mSelection.remove(key);
+            notifyItemStateChanged(key, false);
+            notifySelectionChanged();
+            if (mSelection.isEmpty() && isRangeActive()) {
+                // if there's nothing in the selection and there is an active ranger it results
+                // in unexpected behavior when the user tries to start range selection: the item
+                // which the ranger 'thinks' is the already selected anchor becomes unselectable
+                endRange();
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void startRange(int position) {
+        select(mKeyProvider.getKey(position));
+        anchorRange(position);
+    }
+
+    @Override
+    public void extendRange(int position) {
+        extendRange(position, Range.TYPE_PRIMARY);
+    }
+
+    @Override
+    public void endRange() {
+        mRange = null;
+        // Clean up in case there was any leftover provisional selection
+        clearProvisionalSelection();
+    }
+
+    @Override
+    public void anchorRange(int position) {
+        checkArgument(position != RecyclerView.NO_POSITION);
+        checkArgument(mSelection.contains(mKeyProvider.getKey(position)));
+
+        mRange = new Range(position, mRangeCallbacks);
+    }
+
+    @Override
+    public void extendProvisionalRange(int position) {
+        if (mSingleSelect) {
+            return;
+        }
+
+        if (DEBUG) Log.i(TAG, "Extending provision range to position: " + position);
+        checkState(isRangeActive(), "Range start point not set.");
+        extendRange(position, Range.TYPE_PROVISIONAL);
+    }
+
+    /**
+     * Sets the end point for the current range selection, started by a call to
+     * {@link #startRange(int)}. This function should only be called when a range selection
+     * is active (see {@link #isRangeActive()}. Items in the range [anchor, end] will be
+     * selected or in provisional select, depending on the type supplied. Note that if the type is
+     * provisional selection, one should do {@link #mergeProvisionalSelection()} at some
+     * point before calling on {@link #endRange()}.
+     *
+     * @param position The new end position for the selection range.
+     * @param type The type of selection the range should utilize.
+     */
+    private void extendRange(int position, @RangeType int type) {
+        checkState(isRangeActive(), "Range start point not set.");
+
+        mRange.extendRange(position, type);
+
+        // We're being lazy here notifying even when something might not have changed.
+        // To make this more correct, we'd need to update the Ranger class to return
+        // information about what has changed.
+        notifySelectionChanged();
+    }
+
+    @Override
+    public void setProvisionalSelection(Set<K> newSelection) {
+        if (mSingleSelect) {
+            return;
+        }
+
+        Map<K, Boolean> delta = mSelection.setProvisionalSelection(newSelection);
+        for (Map.Entry<K, Boolean> entry: delta.entrySet()) {
+            notifyItemStateChanged(entry.getKey(), entry.getValue());
+        }
+
+        notifySelectionChanged();
+    }
+
+    @Override
+    public void mergeProvisionalSelection() {
+        mSelection.mergeProvisionalSelection();
+
+        // Note, that for almost all functional purposes, merging a provisional selection
+        // into a the primary selection doesn't change the selection, just an internal
+        // representation of it. But there are some nuanced areas cases where
+        // that isn't true. equality for 1. So, we notify regardless.
+
+        notifySelectionChanged();
+    }
+
+    @Override
+    public void clearProvisionalSelection() {
+        for (K key : mSelection.mProvisionalSelection) {
+            notifyItemStateChanged(key, false);
+        }
+        mSelection.clearProvisionalSelection();
+    }
+
+    @Override
+    public boolean isRangeActive() {
+        return mRange != null;
+    }
+
+    private boolean canSetState(K key, boolean nextState) {
+        return mSelectionPredicate.canSetStateForKey(key, nextState);
+    }
+
+    @Override
+    void onDataSetChanged() {
+        mSelection.clearProvisionalSelection();
+
+        notifySelectionReset();
+
+        for (K key : mSelection) {
+            // If the underlying data set has changed, before restoring
+            // selection we must re-verify that it can be selected.
+            // Why? Because if the dataset has changed, then maybe the
+            // selectability of an item has changed.
+            if (!canSetState(key, true)) {
+                deselect(key);
+            } else {
+                int lastListener = mObservers.size() - 1;
+                for (int i = lastListener; i >= 0; i--) {
+                    mObservers.get(i).onItemStateChanged(key, true);
+                }
+            }
+        }
+
+        notifySelectionChanged();
+    }
+
+    /**
+     * Notifies registered listeners when the selection status of a single item
+     * (identified by {@code position}) changes.
+     */
+    private void notifyItemStateChanged(K key, boolean selected) {
+        checkArgument(key != null);
+
+        int lastListenerIndex = mObservers.size() - 1;
+        for (int i = lastListenerIndex; i >= 0; i--) {
+            mObservers.get(i).onItemStateChanged(key, selected);
+        }
+    }
+
+    private void notifySelectionCleared(Selection<K> selection) {
+        for (K key: selection.mSelection) {
+            notifyItemStateChanged(key, false);
+        }
+        for (K key: selection.mProvisionalSelection) {
+            notifyItemStateChanged(key, false);
+        }
+    }
+
+    /**
+     * Notifies registered listeners when the selection has changed. This
+     * notification should be sent only once a full series of changes
+     * is complete, e.g. clearingSelection, or updating the single
+     * selection from one item to another.
+     */
+    private void notifySelectionChanged() {
+        int lastListenerIndex = mObservers.size() - 1;
+        for (int i = lastListenerIndex; i >= 0; i--) {
+            mObservers.get(i).onSelectionChanged();
+        }
+    }
+
+    private void notifySelectionRestored() {
+        int lastListenerIndex = mObservers.size() - 1;
+        for (int i = lastListenerIndex; i >= 0; i--) {
+            mObservers.get(i).onSelectionRestored();
+        }
+    }
+
+    private void notifySelectionReset() {
+        int lastListenerIndex = mObservers.size() - 1;
+        for (int i = lastListenerIndex; i >= 0; i--) {
+            mObservers.get(i).onSelectionReset();
+        }
+    }
+
+    private void updateForRange(int begin, int end, boolean selected, @RangeType int type) {
+        switch (type) {
+            case Range.TYPE_PRIMARY:
+                updateForRegularRange(begin, end, selected);
+                break;
+            case Range.TYPE_PROVISIONAL:
+                updateForProvisionalRange(begin, end, selected);
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid range type: " + type);
+        }
+    }
+
+    private void updateForRegularRange(int begin, int end, boolean selected) {
+        checkArgument(end >= begin);
+
+        for (int i = begin; i <= end; i++) {
+            K key = mKeyProvider.getKey(i);
+            if (key == null) {
+                continue;
+            }
+
+            if (selected) {
+                select(key);
+            } else {
+                deselect(key);
+            }
+        }
+    }
+
+    private void updateForProvisionalRange(int begin, int end, boolean selected) {
+        checkArgument(end >= begin);
+
+        for (int i = begin; i <= end; i++) {
+            K key = mKeyProvider.getKey(i);
+            if (key == null) {
+                continue;
+            }
+
+            boolean changedState = false;
+            if (selected) {
+                boolean canSelect = canSetState(key, true);
+                if (canSelect && !mSelection.mSelection.contains(key)) {
+                    mSelection.mProvisionalSelection.add(key);
+                    changedState = true;
+                }
+            } else {
+                mSelection.mProvisionalSelection.remove(key);
+                changedState = true;
+            }
+
+            // Only notify item callbacks when something's state is actually changed in provisional
+            // selection.
+            if (changedState) {
+                notifyItemStateChanged(key, selected);
+            }
+        }
+
+        notifySelectionChanged();
+    }
+
+    private final class RangeCallbacks extends Range.Callbacks {
+        @Override
+        void updateForRange(int begin, int end, boolean selected, int type) {
+            switch (type) {
+                case Range.TYPE_PRIMARY:
+                    updateForRegularRange(begin, end, selected);
+                    break;
+                case Range.TYPE_PROVISIONAL:
+                    updateForProvisionalRange(begin, end, selected);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Invalid range type: " + type);
+            }
+        }
+    }
+}
diff --git a/androidx/recyclerview/selection/EventBridge.java b/androidx/recyclerview/selection/EventBridge.java
new file mode 100644
index 0000000..b418ad4
--- /dev/null
+++ b/androidx/recyclerview/selection/EventBridge.java
@@ -0,0 +1,137 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+/**
+ * Provides the necessary glue to notify RecyclerView when selection data changes,
+ * and to notify SelectionHelper when the underlying RecyclerView.Adapter data changes.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+@VisibleForTesting
+public class EventBridge {
+
+    private static final String TAG = "EventsRelays";
+
+    /**
+     * Installs the event bridge for on the supplied adapter/helper.
+     *
+     * @param adapter
+     * @param selectionHelper
+     * @param keyProvider
+     * @param <K>
+     */
+    @VisibleForTesting
+    public static <K> void install(
+            RecyclerView.Adapter<?> adapter,
+            SelectionHelper<K> selectionHelper,
+            ItemKeyProvider<K> keyProvider) {
+        new AdapterToSelectionHelper(adapter, selectionHelper);
+        new SelectionHelperToAdapter<>(selectionHelper, keyProvider, adapter);
+    }
+
+    private static final class AdapterToSelectionHelper extends RecyclerView.AdapterDataObserver {
+
+        private final SelectionHelper<?> mSelectionHelper;
+
+        AdapterToSelectionHelper(
+                RecyclerView.Adapter<?> adapter,
+                SelectionHelper<?> selectionHelper) {
+            adapter.registerAdapterDataObserver(this);
+
+            checkArgument(selectionHelper != null);
+            mSelectionHelper = selectionHelper;
+        }
+
+        @Override
+        public void onChanged() {
+            mSelectionHelper.onDataSetChanged();
+        }
+
+        @Override
+        public void onItemRangeChanged(int startPosition, int itemCount, Object payload) {
+            // No change in position. Ignore, since we assume
+            // selection is a user driven activity. So changes
+            // in properties of items shouldn't result in a
+            // change of selection.
+            // TODO: It is possible properties of items chould change to make them unselectable.
+        }
+
+        @Override
+        public void onItemRangeInserted(int startPosition, int itemCount) {
+            // Uninteresting to us since selection is stable ID based.
+        }
+
+        @Override
+        public void onItemRangeRemoved(int startPosition, int itemCount) {
+            // Uninteresting to us since selection is stable ID based.
+        }
+
+        @Override
+        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+            // Uninteresting to us since selection is stable ID based.
+        }
+    }
+
+    private static final class SelectionHelperToAdapter<K>
+            extends SelectionHelper.SelectionObserver<K> {
+
+        private final ItemKeyProvider<K> mKeyProvider;
+        private final RecyclerView.Adapter<?> mAdapter;
+
+        SelectionHelperToAdapter(
+                SelectionHelper<K> selectionHelper,
+                ItemKeyProvider<K> keyProvider,
+                RecyclerView.Adapter<?> adapter) {
+
+            selectionHelper.addObserver(this);
+
+            checkArgument(keyProvider != null);
+            checkArgument(adapter != null);
+
+            mKeyProvider = keyProvider;
+            mAdapter = adapter;
+        }
+
+        /**
+         * Called when state of an item has been changed.
+         */
+        @Override
+        public void onItemStateChanged(K key, boolean selected) {
+            int position = mKeyProvider.getPosition(key);
+            if (VERBOSE) Log.v(TAG, "ITEM " + key + " CHANGED at pos: " + position);
+
+            if (position < 0) {
+                Log.w(TAG, "Item change notification received for unknown item: " + key);
+                return;
+            }
+
+            mAdapter.notifyItemChanged(position, SelectionHelper.SELECTION_CHANGED_MARKER);
+        }
+    }
+}
diff --git a/androidx/recyclerview/selection/FocusCallbacks.java b/androidx/recyclerview/selection/FocusCallbacks.java
new file mode 100644
index 0000000..4c1c12e
--- /dev/null
+++ b/androidx/recyclerview/selection/FocusCallbacks.java
@@ -0,0 +1,78 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class FocusCallbacks<K> {
+
+    static final <K> FocusCallbacks<K> dummy() {
+        return new FocusCallbacks<K>() {
+            @Override
+            public void focusItem(ItemDetails<K> item) {
+            }
+
+            @Override
+            public boolean hasFocusedItem() {
+                return false;
+            }
+
+            @Override
+            public int getFocusedPosition() {
+                return RecyclerView.NO_POSITION;
+            }
+
+            @Override
+            public void clearFocus() {
+            }
+        };
+    }
+
+    /**
+     * If environment supports focus, focus {@code item}.
+     */
+    public abstract void focusItem(ItemDetails<K> item);
+
+    /**
+     * @return true if there is a focused item.
+     */
+    public abstract boolean hasFocusedItem();
+
+    /**
+     * @return the position of the currently focused item, if any.
+     */
+    public abstract int getFocusedPosition();
+
+    /**
+     * If the environment supports focus and something is focused, unfocus it.
+     */
+    public abstract void clearFocus();
+}
diff --git a/androidx/recyclerview/selection/GestureRouter.java b/androidx/recyclerview/selection/GestureRouter.java
new file mode 100644
index 0000000..82fab87
--- /dev/null
+++ b/androidx/recyclerview/selection/GestureRouter.java
@@ -0,0 +1,100 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.annotation.Nullable;
+import android.view.GestureDetector.OnDoubleTapListener;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+/**
+ * GestureRouter is responsible for routing gestures detected by a GestureDetector
+ * to registered handlers. The primary function is to divide events by tool-type
+ * allowing handlers to cleanly implement tool-type specific policies.
+ *
+ * @param <T> listener type. Must extend OnGestureListener & OnDoubleTapListener.
+ */
+final class GestureRouter<T extends OnGestureListener & OnDoubleTapListener>
+        implements OnGestureListener, OnDoubleTapListener {
+
+    private final ToolHandlerRegistry<T> mDelegates;
+
+    GestureRouter(T defaultDelegate) {
+        checkArgument(defaultDelegate != null);
+        mDelegates = new ToolHandlerRegistry<>(defaultDelegate);
+    }
+
+    GestureRouter() {
+        this((T) new SimpleOnGestureListener());
+    }
+
+    /**
+     * @param toolType
+     * @param delegate the delegate, or null to unregister.
+     */
+    public void register(int toolType, @Nullable T delegate) {
+        mDelegates.set(toolType, delegate);
+    }
+
+    @Override
+    public boolean onSingleTapConfirmed(MotionEvent e) {
+        return mDelegates.get(e).onSingleTapConfirmed(e);
+    }
+
+    @Override
+    public boolean onDoubleTap(MotionEvent e) {
+        return mDelegates.get(e).onDoubleTap(e);
+    }
+
+    @Override
+    public boolean onDoubleTapEvent(MotionEvent e) {
+        return mDelegates.get(e).onDoubleTapEvent(e);
+    }
+
+    @Override
+    public boolean onDown(MotionEvent e) {
+        return mDelegates.get(e).onDown(e);
+    }
+
+    @Override
+    public void onShowPress(MotionEvent e) {
+        mDelegates.get(e).onShowPress(e);
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        return mDelegates.get(e).onSingleTapUp(e);
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        return mDelegates.get(e2).onScroll(e1, e2, distanceX, distanceY);
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+        mDelegates.get(e).onLongPress(e);
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        return mDelegates.get(e2).onFling(e1, e2, velocityX, velocityY);
+    }
+}
diff --git a/androidx/recyclerview/selection/GestureSelectionHelper.java b/androidx/recyclerview/selection/GestureSelectionHelper.java
new file mode 100644
index 0000000..2a28fc5
--- /dev/null
+++ b/androidx/recyclerview/selection/GestureSelectionHelper.java
@@ -0,0 +1,287 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import android.graphics.Point;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * GestureSelectionHelper provides logic that interprets a combination
+ * of motions and gestures in order to provide gesture driven selection support
+ * when used in conjunction with RecyclerView and other classes in the ReyclerView
+ * selection support package.
+ */
+final class GestureSelectionHelper implements OnItemTouchListener {
+
+    private static final String TAG = "GestureSelectionHelper";
+
+    private final SelectionHelper<?> mSelectionMgr;
+    private final AutoScroller mScroller;
+    private final ViewDelegate mView;
+    private final ContentLock mLock;
+
+    private int mLastStartedItemPos = -1;
+    private boolean mStarted = false;
+    private Point mLastInterceptedPoint;
+
+    /**
+     * See {@link #create(SelectionHelper, RecyclerView, AutoScroller, ContentLock)} for convenience
+     * method.
+     */
+    GestureSelectionHelper(
+            SelectionHelper<?> selectionHelper,
+            ViewDelegate view,
+            AutoScroller scroller,
+            ContentLock lock) {
+
+        checkArgument(selectionHelper != null);
+        checkArgument(view != null);
+        checkArgument(scroller != null);
+        checkArgument(lock != null);
+
+        mSelectionMgr = selectionHelper;
+        mView = view;
+        mScroller = scroller;
+        mLock = lock;
+    }
+
+    /**
+     * Explicitly kicks off a gesture multi-select.
+     */
+    void start() {
+        checkState(!mStarted);
+        checkState(mLastStartedItemPos > -1);
+
+        // Partner code in MotionInputHandler ensures items
+        // are selected and range established prior to
+        // start being called.
+        // Verify the truth of that statement here
+        // to make the implicit coupling less of a time bomb.
+        checkState(mSelectionMgr.isRangeActive());
+
+        mLock.checkUnlocked();
+
+        mStarted = true;
+        mLock.block();
+    }
+
+    @Override
+    /** @hide */
+    public boolean onInterceptTouchEvent(RecyclerView unused, MotionEvent e) {
+        if (MotionEvents.isMouseEvent(e)) {
+            if (Shared.DEBUG) Log.w(TAG, "Unexpected Mouse event. Check configuration.");
+        }
+
+        switch (e.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                // NOTE: Unlike events with other actions, RecyclerView eats
+                // "DOWN" events. So even if we return true here we'll
+                // never see an event w/ ACTION_DOWN passed to onTouchEvent.
+                return handleInterceptedDownEvent(e);
+            case MotionEvent.ACTION_MOVE:
+                return mStarted;
+        }
+
+        return false;
+    }
+
+    @Override
+    /** @hide */
+    public void onTouchEvent(RecyclerView unused, MotionEvent e) {
+        checkState(mStarted);
+
+        switch (e.getActionMasked()) {
+            case MotionEvent.ACTION_MOVE:
+                handleMoveEvent(e);
+                break;
+            case MotionEvent.ACTION_UP:
+                handleUpEvent(e);
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                handleCancelEvent(e);
+                break;
+        }
+    }
+
+    @Override
+    /** @hide */
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+    }
+
+    // Called when an ACTION_DOWN event is intercepted.
+    // If down event happens on an item, we mark that item's position as last started.
+    private boolean handleInterceptedDownEvent(MotionEvent e) {
+        mLastStartedItemPos = mView.getItemUnder(e);
+        return mLastStartedItemPos != RecyclerView.NO_POSITION;
+    }
+
+    // Called when ACTION_UP event is to be handled.
+    // Essentially, since this means all gesture movement is over, reset everything and apply
+    // provisional selection.
+    private void handleUpEvent(MotionEvent e) {
+        mSelectionMgr.mergeProvisionalSelection();
+        endSelection();
+        if (mLastStartedItemPos > -1) {
+            mSelectionMgr.startRange(mLastStartedItemPos);
+        }
+    }
+
+    // Called when ACTION_CANCEL event is to be handled.
+    // This means this gesture selection is aborted, so reset everything and abandon provisional
+    // selection.
+    private void handleCancelEvent(MotionEvent unused) {
+        mSelectionMgr.clearProvisionalSelection();
+        endSelection();
+    }
+
+    private void endSelection() {
+        checkState(mStarted);
+
+        mLastStartedItemPos = -1;
+        mStarted = false;
+        mScroller.reset();
+        mLock.unblock();
+    }
+
+    // Call when an intercepted ACTION_MOVE event is passed down.
+    // At this point, we are sure user wants to gesture multi-select.
+    private void handleMoveEvent(MotionEvent e) {
+        mLastInterceptedPoint = MotionEvents.getOrigin(e);
+
+        int lastGlidedItemPos = mView.getLastGlidedItemPosition(e);
+        if (lastGlidedItemPos != RecyclerView.NO_POSITION) {
+            extendSelection(lastGlidedItemPos);
+        }
+
+        mScroller.scroll(mLastInterceptedPoint);
+    }
+
+    // It's possible for events to go over the top/bottom of the RecyclerView.
+    // We want to get a Y-coordinate within the RecyclerView so we can find the childView underneath
+    // correctly.
+    private static float getInboundY(float max, float y) {
+        if (y < 0f) {
+            return 0f;
+        } else if (y > max) {
+            return max;
+        }
+        return y;
+    }
+
+    /* Given the end position, select everything in-between.
+     * @param endPos  The adapter position of the end item.
+     */
+    private void extendSelection(int endPos) {
+        mSelectionMgr.extendProvisionalRange(endPos);
+    }
+
+    /**
+     * Returns a new instance of GestureSelectionHelper.
+     */
+    static GestureSelectionHelper create(
+            SelectionHelper selectionMgr,
+            RecyclerView recView,
+            AutoScroller scroller,
+            ContentLock lock) {
+
+        return new GestureSelectionHelper(
+                selectionMgr,
+                new RecyclerViewDelegate(recView),
+                scroller,
+                lock);
+    }
+
+    @VisibleForTesting
+    abstract static class ViewDelegate {
+        abstract int getHeight();
+
+        abstract int getItemUnder(MotionEvent e);
+
+        abstract int getLastGlidedItemPosition(MotionEvent e);
+    }
+
+    @VisibleForTesting
+    static final class RecyclerViewDelegate extends ViewDelegate {
+
+        private final RecyclerView mRecView;
+
+        RecyclerViewDelegate(RecyclerView view) {
+            checkArgument(view != null);
+            mRecView = view;
+        }
+
+        @Override
+        int getHeight() {
+            return mRecView.getHeight();
+        }
+
+        @Override
+        int getItemUnder(MotionEvent e) {
+            View child = mRecView.findChildViewUnder(e.getX(), e.getY());
+            return child != null
+                    ? mRecView.getChildAdapterPosition(child)
+                    : RecyclerView.NO_POSITION;
+        }
+
+        @Override
+        int getLastGlidedItemPosition(MotionEvent e) {
+            // If user has moved his pointer to the bottom-right empty pane (ie. to the right of the
+            // last item of the recycler view), we would want to set that as the currentItemPos
+            View lastItem = mRecView.getLayoutManager()
+                    .getChildAt(mRecView.getLayoutManager().getChildCount() - 1);
+            int direction = ViewCompat.getLayoutDirection(mRecView);
+            final boolean pastLastItem = isPastLastItem(lastItem.getTop(),
+                    lastItem.getLeft(),
+                    lastItem.getRight(),
+                    e,
+                    direction);
+
+            // Since views get attached & detached from RecyclerView,
+            // {@link LayoutManager#getChildCount} can return a different number from the actual
+            // number
+            // of items in the adapter. Using the adapter is the for sure way to get the actual last
+            // item position.
+            final float inboundY = getInboundY(mRecView.getHeight(), e.getY());
+            return (pastLastItem) ? mRecView.getAdapter().getItemCount() - 1
+                    : mRecView.getChildAdapterPosition(
+                            mRecView.findChildViewUnder(e.getX(), inboundY));
+        }
+
+        /*
+         * Check to see if MotionEvent if past a particular item, i.e. to the right or to the bottom
+         * of the item.
+         * For RTL, it would to be to the left or to the bottom of the item.
+         */
+        @VisibleForTesting
+        static boolean isPastLastItem(int top, int left, int right, MotionEvent e, int direction) {
+            if (direction == View.LAYOUT_DIRECTION_LTR) {
+                return e.getX() > right && e.getY() > top;
+            } else {
+                return e.getX() < left && e.getY() > top;
+            }
+        }
+    }
+}
diff --git a/androidx/recyclerview/selection/GridModel.java b/androidx/recyclerview/selection/GridModel.java
new file mode 100644
index 0000000..4358958
--- /dev/null
+++ b/androidx/recyclerview/selection/GridModel.java
@@ -0,0 +1,786 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Provides a band selection item model for views within a RecyclerView. This class queries the
+ * RecyclerView to determine where its items are placed; then, once band selection is underway,
+ * it alerts listeners of which items are covered by the selections.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ */
+final class GridModel<K> {
+
+    // Magical value indicating that a value has not been previously set. primitive null :)
+    static final int NOT_SET = -1;
+
+    // Enum values used to determine the corner at which the origin is located within the
+    private static final int UPPER = 0x00;
+    private static final int LOWER = 0x01;
+    private static final int LEFT = 0x00;
+    private static final int RIGHT = 0x02;
+    private static final int UPPER_LEFT = UPPER | LEFT;
+    private static final int UPPER_RIGHT = UPPER | RIGHT;
+    private static final int LOWER_LEFT = LOWER | LEFT;
+    private static final int LOWER_RIGHT = LOWER | RIGHT;
+
+    private final GridHost<K> mHost;
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final SelectionPredicate<K> mSelectionPredicate;
+
+    private final List<SelectionObserver> mOnSelectionChangedListeners = new ArrayList<>();
+
+    // Map from the x-value of the left side of a SparseBooleanArray of adapter positions, keyed
+    // by their y-offset. For example, if the first column of the view starts at an x-value of 5,
+    // mColumns.get(5) would return an array of positions in that column. Within that array, the
+    // value for key y is the adapter position for the item whose y-offset is y.
+    private final SparseArray<SparseIntArray> mColumns = new SparseArray<>();
+
+    // List of limits along the x-axis (columns).
+    // This list is sorted from furthest left to furthest right.
+    private final List<Limits> mColumnBounds = new ArrayList<>();
+
+    // List of limits along the y-axis (rows). Note that this list only contains items which
+    // have been in the viewport.
+    private final List<Limits> mRowBounds = new ArrayList<>();
+
+    // The adapter positions which have been recorded so far.
+    private final SparseBooleanArray mKnownPositions = new SparseBooleanArray();
+
+    // Array passed to registered OnSelectionChangedListeners. One array is created and reused
+    // throughout the lifetime of the object.
+    private final Set<K> mSelection = new HashSet<>();
+
+    // The current pointer (in absolute positioning from the top of the view).
+    private Point mPointer;
+
+    // The bounds of the band selection.
+    private RelativePoint mRelOrigin;
+    private RelativePoint mRelPointer;
+
+    private boolean mIsActive;
+
+    // Tracks where the band select originated from. This is used to determine where selections
+    // should expand from when Shift+click is used.
+    private int mPositionNearestOrigin = NOT_SET;
+
+    private final OnScrollListener mScrollListener;
+
+    GridModel(
+            GridHost host,
+            ItemKeyProvider<K> keyProvider,
+            SelectionPredicate<K> selectionPredicate) {
+
+        checkArgument(host != null);
+        checkArgument(keyProvider != null);
+        checkArgument(selectionPredicate != null);
+
+        mHost = host;
+        mKeyProvider = keyProvider;
+        mSelectionPredicate = selectionPredicate;
+
+        mScrollListener = new OnScrollListener() {
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                GridModel.this.onScrolled(recyclerView, dx, dy);
+            }
+        };
+
+        mHost.addOnScrollListener(mScrollListener);
+    }
+
+    /**
+     * Start a band select operation at the given point.
+     *
+     * @param relativeOrigin The origin of the band select operation, relative to the viewport.
+     *                       For example, if the view is scrolled to the bottom, the top-left of
+     *                       the
+     *                       viewport
+     *                       would have a relative origin of (0, 0), even though its absolute point
+     *                       has a higher
+     *                       y-value.
+     */
+    void startCapturing(Point relativeOrigin) {
+        recordVisibleChildren();
+        if (isEmpty()) {
+            // The selection band logic works only if there is at least one visible child.
+            return;
+        }
+
+        mIsActive = true;
+        mPointer = mHost.createAbsolutePoint(relativeOrigin);
+        mRelOrigin = createRelativePoint(mPointer);
+        mRelPointer = createRelativePoint(mPointer);
+        computeCurrentSelection();
+        notifySelectionChanged();
+    }
+
+    /**
+     * Ends the band selection.
+     */
+    void stopCapturing() {
+        mIsActive = false;
+    }
+
+    /**
+     * Resizes the selection by adjusting the pointer (i.e., the corner of the selection
+     * opposite the origin.
+     *
+     * @param relativePointer The pointer (opposite of the origin) of the band select operation,
+     *                        relative to the viewport. For example, if the view is scrolled to the
+     *                        bottom, the
+     *                        top-left of the viewport would have a relative origin of (0, 0), even
+     *                        though its
+     *                        absolute point has a higher y-value.
+     */
+    @VisibleForTesting
+    void resizeSelection(Point relativePointer) {
+        mPointer = mHost.createAbsolutePoint(relativePointer);
+        updateModel();
+    }
+
+    /**
+     * @return The adapter position for the item nearest the origin corresponding to the latest
+     * band select operation, or NOT_SET if the selection did not cover any items.
+     */
+    int getPositionNearestOrigin() {
+        return mPositionNearestOrigin;
+    }
+
+    private void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+        if (!mIsActive) {
+            return;
+        }
+
+        mPointer.x += dx;
+        mPointer.y += dy;
+        recordVisibleChildren();
+        updateModel();
+    }
+
+    /**
+     * Queries the view for all children and records their location metadata.
+     */
+    private void recordVisibleChildren() {
+        for (int i = 0; i < mHost.getVisibleChildCount(); i++) {
+            int adapterPosition = mHost.getAdapterPositionAt(i);
+            // Sometimes the view is not attached, as we notify the multi selection manager
+            // synchronously, while views are attached asynchronously. As a result items which
+            // are in the adapter may not actually have a corresponding view (yet).
+            if (mHost.hasView(adapterPosition)
+                    && mSelectionPredicate.canSetStateAtPosition(adapterPosition, true)
+                    && !mKnownPositions.get(adapterPosition)) {
+                mKnownPositions.put(adapterPosition, true);
+                recordItemData(mHost.getAbsoluteRectForChildViewAt(i), adapterPosition);
+            }
+        }
+    }
+
+    /**
+     * Checks if there are any recorded children.
+     */
+    private boolean isEmpty() {
+        return mColumnBounds.size() == 0 || mRowBounds.size() == 0;
+    }
+
+    /**
+     * Updates the limits lists and column map with the given item metadata.
+     *
+     * @param absoluteChildRect The absolute rectangle for the child view being processed.
+     * @param adapterPosition   The position of the child view being processed.
+     */
+    private void recordItemData(Rect absoluteChildRect, int adapterPosition) {
+        if (mColumnBounds.size() != mHost.getColumnCount()) {
+            // If not all x-limits have been recorded, record this one.
+            recordLimits(
+                    mColumnBounds, new Limits(absoluteChildRect.left, absoluteChildRect.right));
+        }
+
+        recordLimits(mRowBounds, new Limits(absoluteChildRect.top, absoluteChildRect.bottom));
+
+        SparseIntArray columnList = mColumns.get(absoluteChildRect.left);
+        if (columnList == null) {
+            columnList = new SparseIntArray();
+            mColumns.put(absoluteChildRect.left, columnList);
+        }
+        columnList.put(absoluteChildRect.top, adapterPosition);
+    }
+
+    /**
+     * Ensures limits exists within the sorted list limitsList, and adds it to the list if it
+     * does not exist.
+     */
+    private void recordLimits(List<Limits> limitsList, Limits limits) {
+        int index = Collections.binarySearch(limitsList, limits);
+        if (index < 0) {
+            limitsList.add(~index, limits);
+        }
+    }
+
+    /**
+     * Handles a moved pointer; this function determines whether the pointer movement resulted
+     * in a selection change and, if it has, notifies listeners of this change.
+     */
+    private void updateModel() {
+        RelativePoint old = mRelPointer;
+        mRelPointer = createRelativePoint(mPointer);
+        if (old != null && mRelPointer.equals(old)) {
+            return;
+        }
+
+        computeCurrentSelection();
+        notifySelectionChanged();
+    }
+
+    /**
+     * Computes the currently-selected items.
+     */
+    private void computeCurrentSelection() {
+        if (areItemsCoveredByBand(mRelPointer, mRelOrigin)) {
+            updateSelection(computeBounds());
+        } else {
+            mSelection.clear();
+            mPositionNearestOrigin = NOT_SET;
+        }
+    }
+
+    /**
+     * Notifies all listeners of a selection change. Note that this function simply passes
+     * mSelection, so computeCurrentSelection() should be called before this
+     * function.
+     */
+    private void notifySelectionChanged() {
+        for (SelectionObserver listener : mOnSelectionChangedListeners) {
+            listener.onSelectionChanged(mSelection);
+        }
+    }
+
+    /**
+     * @param rect Rectangle including all covered items.
+     */
+    private void updateSelection(Rect rect) {
+        int columnStart =
+                Collections.binarySearch(mColumnBounds, new Limits(rect.left, rect.left));
+
+        checkArgument(columnStart >= 0, "Rect doesn't intesect any known column.");
+
+        int columnEnd = columnStart;
+
+        for (int i = columnStart; i < mColumnBounds.size()
+                && mColumnBounds.get(i).lowerLimit <= rect.right; i++) {
+            columnEnd = i;
+        }
+
+        int rowStart = Collections.binarySearch(mRowBounds, new Limits(rect.top, rect.top));
+        if (rowStart < 0) {
+            mPositionNearestOrigin = NOT_SET;
+            return;
+        }
+
+        int rowEnd = rowStart;
+        for (int i = rowStart; i < mRowBounds.size()
+                && mRowBounds.get(i).lowerLimit <= rect.bottom; i++) {
+            rowEnd = i;
+        }
+
+        updateSelection(columnStart, columnEnd, rowStart, rowEnd);
+    }
+
+    /**
+     * Computes the selection given the previously-computed start- and end-indices for each
+     * row and column.
+     */
+    private void updateSelection(
+            int columnStartIndex, int columnEndIndex, int rowStartIndex, int rowEndIndex) {
+
+        if (BandSelectionHelper.DEBUG) {
+            Log.d(BandSelectionHelper.TAG, String.format(
+                    "updateSelection: %d, %d, %d, %d",
+                    columnStartIndex, columnEndIndex, rowStartIndex, rowEndIndex));
+        }
+
+        mSelection.clear();
+        for (int column = columnStartIndex; column <= columnEndIndex; column++) {
+            SparseIntArray items = mColumns.get(mColumnBounds.get(column).lowerLimit);
+            for (int row = rowStartIndex; row <= rowEndIndex; row++) {
+                // The default return value for SparseIntArray.get is 0, which is a valid
+                // position. Use a sentry value to prevent erroneously selecting item 0.
+                final int rowKey = mRowBounds.get(row).lowerLimit;
+                int position = items.get(rowKey, NOT_SET);
+                if (position != NOT_SET) {
+                    K key = mKeyProvider.getKey(position);
+                    if (key != null) {
+                        // The adapter inserts items for UI layout purposes that aren't
+                        // associated with files. Those will have a null model ID.
+                        // Don't select them.
+                        if (canSelect(key)) {
+                            mSelection.add(key);
+                        }
+                    }
+                    if (isPossiblePositionNearestOrigin(column, columnStartIndex, columnEndIndex,
+                            row, rowStartIndex, rowEndIndex)) {
+                        // If this is the position nearest the origin, record it now so that it
+                        // can be returned by endSelection() later.
+                        mPositionNearestOrigin = position;
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean canSelect(K key) {
+        return mSelectionPredicate.canSetStateForKey(key, true);
+    }
+
+    /**
+     * @return Returns true if the position is the nearest to the origin, or, in the case of the
+     * lower-right corner, whether it is possible that the position is the nearest to the
+     * origin. See comment below for reasoning for this special case.
+     */
+    private boolean isPossiblePositionNearestOrigin(int columnIndex, int columnStartIndex,
+            int columnEndIndex, int rowIndex, int rowStartIndex, int rowEndIndex) {
+        int corner = computeCornerNearestOrigin();
+        switch (corner) {
+            case UPPER_LEFT:
+                return columnIndex == columnStartIndex && rowIndex == rowStartIndex;
+            case UPPER_RIGHT:
+                return columnIndex == columnEndIndex && rowIndex == rowStartIndex;
+            case LOWER_LEFT:
+                return columnIndex == columnStartIndex && rowIndex == rowEndIndex;
+            case LOWER_RIGHT:
+                // Note that in some cases, the last row will not have as many items as there
+                // are columns (e.g., if there are 4 items and 3 columns, the second row will
+                // only have one item in the first column). This function is invoked for each
+                // position from left to right, so return true for any position in the bottom
+                // row and only the right-most position in the bottom row will be recorded.
+                return rowIndex == rowEndIndex;
+            default:
+                throw new RuntimeException("Invalid corner type.");
+        }
+    }
+
+    /**
+     * Listener for changes in which items have been band selected.
+     */
+    public abstract static class SelectionObserver<K> {
+        abstract void onSelectionChanged(Set<K> updatedSelection);
+    }
+
+    void addOnSelectionChangedListener(SelectionObserver listener) {
+        mOnSelectionChangedListeners.add(listener);
+    }
+
+    /**
+     * Called when {@link BandSelectionHelper} is finished with a GridModel.
+     */
+    void onDestroy() {
+        mOnSelectionChangedListeners.clear();
+        // Cleanup listeners to prevent memory leaks.
+        mHost.removeOnScrollListener(mScrollListener);
+    }
+
+    /**
+     * Limits of a view item. For example, if an item's left side is at x-value 5 and its right side
+     * is at x-value 10, the limits would be from 5 to 10. Used to record the left- and right sides
+     * of item columns and the top- and bottom sides of item rows so that it can be determined
+     * whether the pointer is located within the bounds of an item.
+     */
+    private static class Limits implements Comparable<Limits> {
+        public int lowerLimit;
+        public int upperLimit;
+
+        Limits(int lowerLimit, int upperLimit) {
+            this.lowerLimit = lowerLimit;
+            this.upperLimit = upperLimit;
+        }
+
+        @Override
+        public int compareTo(Limits other) {
+            return lowerLimit - other.lowerLimit;
+        }
+
+        @Override
+        public int hashCode() {
+            return lowerLimit ^ upperLimit;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof Limits)) {
+                return false;
+            }
+
+            return ((Limits) other).lowerLimit == lowerLimit
+                    && ((Limits) other).upperLimit == upperLimit;
+        }
+
+        @Override
+        public String toString() {
+            return "(" + lowerLimit + ", " + upperLimit + ")";
+        }
+    }
+
+    /**
+     * The location of a coordinate relative to items. This class represents a general area of the
+     * view as it relates to band selection rather than an explicit point. For example, two
+     * different points within an item are considered to have the same "location" because band
+     * selection originating within the item would select the same items no matter which point
+     * was used. Same goes for points between items as well as those at the very beginning or end
+     * of the view.
+     *
+     * Tracking a coordinate (e.g., an x-value) as a CoordinateLocation instead of as an int has the
+     * advantage of tying the value to the Limits of items along that axis. This allows easy
+     * selection of items within those Limits as opposed to a search through every item to see if a
+     * given coordinate value falls within those Limits.
+     */
+    private static class RelativeCoordinate
+            implements Comparable<RelativeCoordinate> {
+        /**
+         * Location describing points after the last known item.
+         */
+        static final int AFTER_LAST_ITEM = 0;
+
+        /**
+         * Location describing points before the first known item.
+         */
+        static final int BEFORE_FIRST_ITEM = 1;
+
+        /**
+         * Location describing points between two items.
+         */
+        static final int BETWEEN_TWO_ITEMS = 2;
+
+        /**
+         * Location describing points within the limits of one item.
+         */
+        static final int WITHIN_LIMITS = 3;
+
+        /**
+         * The type of this coordinate, which is one of AFTER_LAST_ITEM, BEFORE_FIRST_ITEM,
+         * BETWEEN_TWO_ITEMS, or WITHIN_LIMITS.
+         */
+        public final int type;
+
+        /**
+         * The limits before the coordinate; only populated when type == WITHIN_LIMITS or type ==
+         * BETWEEN_TWO_ITEMS.
+         */
+        public Limits limitsBeforeCoordinate;
+
+        /**
+         * The limits after the coordinate; only populated when type == BETWEEN_TWO_ITEMS.
+         */
+        public Limits limitsAfterCoordinate;
+
+        // Limits of the first known item; only populated when type == BEFORE_FIRST_ITEM.
+        public Limits mFirstKnownItem;
+        // Limits of the last known item; only populated when type == AFTER_LAST_ITEM.
+        public Limits mLastKnownItem;
+
+        /**
+         * @param limitsList The sorted limits list for the coordinate type. If this
+         *                   CoordinateLocation is an x-value, mXLimitsList should be passed;
+         *                   otherwise,
+         *                   mYLimitsList should be pased.
+         * @param value      The coordinate value.
+         */
+        RelativeCoordinate(List<Limits> limitsList, int value) {
+            int index = Collections.binarySearch(limitsList, new Limits(value, value));
+
+            if (index >= 0) {
+                this.type = WITHIN_LIMITS;
+                this.limitsBeforeCoordinate = limitsList.get(index);
+            } else if (~index == 0) {
+                this.type = BEFORE_FIRST_ITEM;
+                this.mFirstKnownItem = limitsList.get(0);
+            } else if (~index == limitsList.size()) {
+                Limits lastLimits = limitsList.get(limitsList.size() - 1);
+                if (lastLimits.lowerLimit <= value && value <= lastLimits.upperLimit) {
+                    this.type = WITHIN_LIMITS;
+                    this.limitsBeforeCoordinate = lastLimits;
+                } else {
+                    this.type = AFTER_LAST_ITEM;
+                    this.mLastKnownItem = lastLimits;
+                }
+            } else {
+                Limits limitsBeforeIndex = limitsList.get(~index - 1);
+                if (limitsBeforeIndex.lowerLimit <= value
+                        && value <= limitsBeforeIndex.upperLimit) {
+                    this.type = WITHIN_LIMITS;
+                    this.limitsBeforeCoordinate = limitsList.get(~index - 1);
+                } else {
+                    this.type = BETWEEN_TWO_ITEMS;
+                    this.limitsBeforeCoordinate = limitsList.get(~index - 1);
+                    this.limitsAfterCoordinate = limitsList.get(~index);
+                }
+            }
+        }
+
+        int toComparisonValue() {
+            if (type == BEFORE_FIRST_ITEM) {
+                return mFirstKnownItem.lowerLimit - 1;
+            } else if (type == AFTER_LAST_ITEM) {
+                return mLastKnownItem.upperLimit + 1;
+            } else if (type == BETWEEN_TWO_ITEMS) {
+                return limitsBeforeCoordinate.upperLimit + 1;
+            } else {
+                return limitsBeforeCoordinate.lowerLimit;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return mFirstKnownItem.lowerLimit
+                    ^ mLastKnownItem.upperLimit
+                    ^ limitsBeforeCoordinate.upperLimit
+                    ^ limitsBeforeCoordinate.lowerLimit;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof RelativeCoordinate)) {
+                return false;
+            }
+
+            RelativeCoordinate otherCoordinate = (RelativeCoordinate) other;
+            return toComparisonValue() == otherCoordinate.toComparisonValue();
+        }
+
+        @Override
+        public int compareTo(RelativeCoordinate other) {
+            return toComparisonValue() - other.toComparisonValue();
+        }
+    }
+
+    RelativePoint createRelativePoint(Point point) {
+        return new RelativePoint(
+                new RelativeCoordinate(mColumnBounds, point.x),
+                new RelativeCoordinate(mRowBounds, point.y));
+    }
+
+    /**
+     * The location of a point relative to the Limits of nearby items; consists of both an x- and
+     * y-RelativeCoordinateLocation.
+     */
+    private static class RelativePoint {
+
+        final RelativeCoordinate mX;
+        final RelativeCoordinate mY;
+
+        RelativePoint(List<Limits> columnLimits, List<Limits> rowLimits, Point point) {
+            this.mX = new RelativeCoordinate(columnLimits, point.x);
+            this.mY = new RelativeCoordinate(rowLimits, point.y);
+        }
+
+        RelativePoint(RelativeCoordinate x, RelativeCoordinate y) {
+            this.mX = x;
+            this.mY = y;
+        }
+
+        @Override
+        public int hashCode() {
+            return mX.toComparisonValue() ^ mY.toComparisonValue();
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof RelativePoint)) {
+                return false;
+            }
+
+            RelativePoint otherPoint = (RelativePoint) other;
+            return mX.equals(otherPoint.mX) && mY.equals(otherPoint.mY);
+        }
+    }
+
+    /**
+     * Generates a rectangle which contains the items selected by the pointer and origin.
+     *
+     * @return The rectangle, or null if no items were selected.
+     */
+    private Rect computeBounds() {
+        Rect rect = new Rect();
+        rect.left = getCoordinateValue(
+                min(mRelOrigin.mX, mRelPointer.mX),
+                mColumnBounds,
+                true);
+        rect.right = getCoordinateValue(
+                max(mRelOrigin.mX, mRelPointer.mX),
+                mColumnBounds,
+                false);
+        rect.top = getCoordinateValue(
+                min(mRelOrigin.mY, mRelPointer.mY),
+                mRowBounds,
+                true);
+        rect.bottom = getCoordinateValue(
+                max(mRelOrigin.mY, mRelPointer.mY),
+                mRowBounds,
+                false);
+        return rect;
+    }
+
+    /**
+     * Computes the corner of the selection nearest the origin.
+     */
+    private int computeCornerNearestOrigin() {
+        int cornerValue = 0;
+
+        if (mRelOrigin.mY.equals(min(mRelOrigin.mY, mRelPointer.mY))) {
+            cornerValue |= UPPER;
+        } else {
+            cornerValue |= LOWER;
+        }
+
+        if (mRelOrigin.mX.equals(min(mRelOrigin.mX, mRelPointer.mX))) {
+            cornerValue |= LEFT;
+        } else {
+            cornerValue |= RIGHT;
+        }
+
+        return cornerValue;
+    }
+
+    private RelativeCoordinate min(RelativeCoordinate first, RelativeCoordinate second) {
+        return first.compareTo(second) < 0 ? first : second;
+    }
+
+    private RelativeCoordinate max(RelativeCoordinate first, RelativeCoordinate second) {
+        return first.compareTo(second) > 0 ? first : second;
+    }
+
+    /**
+     * @return The absolute coordinate (i.e., the x- or y-value) of the given relative
+     * coordinate.
+     */
+    private int getCoordinateValue(
+            RelativeCoordinate coordinate, List<Limits> limitsList, boolean isStartOfRange) {
+
+        switch (coordinate.type) {
+            case RelativeCoordinate.BEFORE_FIRST_ITEM:
+                return limitsList.get(0).lowerLimit;
+            case RelativeCoordinate.AFTER_LAST_ITEM:
+                return limitsList.get(limitsList.size() - 1).upperLimit;
+            case RelativeCoordinate.BETWEEN_TWO_ITEMS:
+                if (isStartOfRange) {
+                    return coordinate.limitsAfterCoordinate.lowerLimit;
+                } else {
+                    return coordinate.limitsBeforeCoordinate.upperLimit;
+                }
+            case RelativeCoordinate.WITHIN_LIMITS:
+                return coordinate.limitsBeforeCoordinate.lowerLimit;
+        }
+
+        throw new RuntimeException("Invalid coordinate value.");
+    }
+
+    private boolean areItemsCoveredByBand(
+            RelativePoint first, RelativePoint second) {
+
+        return doesCoordinateLocationCoverItems(first.mX, second.mX)
+                && doesCoordinateLocationCoverItems(first.mY, second.mY);
+    }
+
+    private boolean doesCoordinateLocationCoverItems(
+            RelativeCoordinate pointerCoordinate, RelativeCoordinate originCoordinate) {
+
+        if (pointerCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM
+                && originCoordinate.type == RelativeCoordinate.BEFORE_FIRST_ITEM) {
+            return false;
+        }
+
+        if (pointerCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM
+                && originCoordinate.type == RelativeCoordinate.AFTER_LAST_ITEM) {
+            return false;
+        }
+
+        if (pointerCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS
+                && originCoordinate.type == RelativeCoordinate.BETWEEN_TWO_ITEMS
+                && pointerCoordinate.limitsBeforeCoordinate.equals(
+                originCoordinate.limitsBeforeCoordinate)
+                && pointerCoordinate.limitsAfterCoordinate.equals(
+                originCoordinate.limitsAfterCoordinate)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Provides functionality for BandController. Exists primarily to tests that are
+     * fully isolated from RecyclerView.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     */
+    abstract static class GridHost<K> extends BandSelectionHelper.BandHost<K> {
+
+        /**
+         * Remove the listener.
+         *
+         * @param listener
+         */
+        abstract void removeOnScrollListener(RecyclerView.OnScrollListener listener);
+
+        /**
+         * @param relativePoint for which to create absolute point.
+         * @return absolute point.
+         */
+        abstract Point createAbsolutePoint(Point relativePoint);
+
+        /**
+         * @param index index of child.
+         * @return rectangle describing child at {@code index}.
+         */
+        abstract Rect getAbsoluteRectForChildViewAt(int index);
+
+        /**
+         * @param index index of child.
+         * @return child adapter position for the child at {@code index}
+         */
+        abstract int getAdapterPositionAt(int index);
+
+        /** @return column count. */
+        abstract int getColumnCount();
+
+        /** @return number of children visible in the view. */
+        abstract int getVisibleChildCount();
+
+        /**
+         * @return true if the item at adapter position is attached to a view.
+         */
+        abstract boolean hasView(int adapterPosition);
+    }
+}
diff --git a/androidx/recyclerview/selection/ItemDetailsLookup.java b/androidx/recyclerview/selection/ItemDetailsLookup.java
new file mode 100644
index 0000000..da30c97
--- /dev/null
+++ b/androidx/recyclerview/selection/ItemDetailsLookup.java
@@ -0,0 +1,149 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.view.MotionEvent;
+
+/**
+ * Provides event handlers w/ access to details about documents details
+ * view items Documents in the UI (RecyclerView).
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class ItemDetailsLookup<K> {
+
+    /** @return true if there is an item under the finger/cursor. */
+    public boolean overItem(MotionEvent e) {
+        return getItemPosition(e) != RecyclerView.NO_POSITION;
+    }
+
+    /** @return true if there is an item w/ a stable ID under the finger/cursor. */
+    public boolean overItemWithSelectionKey(MotionEvent e) {
+        return overItem(e) && hasSelectionKey(getItemDetails(e));
+    }
+
+    /**
+     * @return true if the event is over an area that can be dragged via touch
+     * or via mouse. List items have a white area that is not draggable.
+     */
+    public boolean inItemDragRegion(MotionEvent e) {
+        return overItem(e) && getItemDetails(e).inDragRegion(e);
+    }
+
+    /**
+     * @return true if the event is in the "selection hot spot" region.
+     * The hot spot region instantly selects in touch mode, vs launches.
+     */
+    public boolean inItemSelectRegion(MotionEvent e) {
+        return overItem(e) && getItemDetails(e).inSelectionHotspot(e);
+    }
+
+    /**
+     * @return the adapter position of the item under the finger/cursor.
+     */
+    public int getItemPosition(MotionEvent e) {
+        @Nullable ItemDetails<?> item = getItemDetails(e);
+        return item != null
+                ? item.getPosition()
+                : RecyclerView.NO_POSITION;
+    }
+
+    private static boolean hasSelectionKey(@Nullable ItemDetails<?> item) {
+        return item != null && item.getSelectionKey() != null;
+    }
+
+    private static boolean hasPosition(@Nullable ItemDetails<?> item) {
+        return item != null && item.getPosition() != RecyclerView.NO_POSITION;
+    }
+
+    /**
+     * @return the DocumentDetails for the item under the event, or null.
+     */
+    public abstract @Nullable ItemDetails<K> getItemDetails(MotionEvent e);
+
+    /**
+     * Abstract class providing helper classes with access to information about
+     * RecyclerView item associated with a MotionEvent.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     */
+    // TODO: Can this be merged with ViewHolder?
+    public abstract static class ItemDetails<K> {
+
+        /** @return the position of an item. */
+        public abstract int getPosition();
+
+        /** @return true if the item has a stable id. */
+        public boolean hasSelectionKey() {
+            return getSelectionKey() != null;
+        }
+
+        /** @return the stable id of an item. */
+        public abstract @Nullable K getSelectionKey();
+
+        /**
+         * @return true if the event is in an area of the item that should be
+         * directly interpreted as a user wishing to select the item. This
+         * is useful for checkboxes and other UI affordances focused on enabling
+         * selection.
+         */
+        public boolean inSelectionHotspot(MotionEvent e) {
+            return false;
+        }
+
+        /**
+         * Events in the drag region will dealt with differently that events outside
+         * of the drag region. This allows the client to implement custom handling
+         * for events related to drag and drop.
+         */
+        public boolean inDragRegion(MotionEvent e) {
+            return false;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ItemDetails) {
+                return isEqualTo((ItemDetails) obj);
+            }
+            return false;
+        }
+
+        private boolean isEqualTo(ItemDetails other) {
+            K key = getSelectionKey();
+            boolean sameKeys = false;
+            if (key == null) {
+                sameKeys = other.getSelectionKey() == null;
+            } else {
+                sameKeys = key.equals(other.getSelectionKey());
+            }
+            return sameKeys && this.getPosition() == other.getPosition();
+        }
+
+        @Override
+        public int hashCode() {
+            return getPosition() >>> 8;
+        }
+    }
+}
diff --git a/androidx/recyclerview/selection/ItemKeyProvider.java b/androidx/recyclerview/selection/ItemKeyProvider.java
new file mode 100644
index 0000000..134c442
--- /dev/null
+++ b/androidx/recyclerview/selection/ItemKeyProvider.java
@@ -0,0 +1,92 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides support for sting based stable ids in the RecyclerView selection helper.
+ * Client code can use this to look up stable ids when working with selection
+ * in application code.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class ItemKeyProvider<K> {
+
+    /**
+     * Provides access to all data, regardless of whether it is bound to a view or not.
+     * Key providers with this access type enjoy support for enhanced features like:
+     * SHIFT+click range selection, and band selection.
+     */
+    @VisibleForTesting  // otherwise protected would do nicely.
+    public static final int SCOPE_MAPPED = 0;
+
+    /**
+     * Provides access cached data based on what was recently bound in the view.
+     * Employing this provider will result in a reduced feature-set, as some
+     * featuers like SHIFT+click range selection and band selection are dependent
+     * on mapped access.
+     */
+    @VisibleForTesting  // otherwise protected would do nicely.
+    public static final int SCOPE_CACHED = 1;
+
+    @IntDef({
+            SCOPE_MAPPED,
+            SCOPE_CACHED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    protected @interface Scope {}
+
+    private final @Scope int mScope;
+
+    /**
+     * Creates a new provider with the given scope.
+     * @param scope Scope can't change at runtime (at least code won't adapt)
+     *         so it must be specified in the constructor.
+     */
+    protected ItemKeyProvider(@Scope int scope) {
+        checkArgument(scope == SCOPE_MAPPED || scope == SCOPE_CACHED);
+
+        mScope = scope;
+    }
+
+    final boolean hasAccess(@Scope int scope) {
+        return scope == mScope;
+    }
+
+    /**
+     * @return The selection key of the item at the given adapter position.
+     */
+    public abstract @Nullable K getKey(int position);
+
+    /**
+     * @return the position of a stable ID, or RecyclerView.NO_POSITION.
+     */
+    public abstract int getPosition(K key);
+}
diff --git a/androidx/recyclerview/selection/MotionEvents.java b/androidx/recyclerview/selection/MotionEvents.java
new file mode 100644
index 0000000..dd9e54f
--- /dev/null
+++ b/androidx/recyclerview/selection/MotionEvents.java
@@ -0,0 +1,108 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import android.graphics.Point;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Utility methods for working with {@link MotionEvent} instances.
+ */
+final class MotionEvents {
+
+    private MotionEvents() {}
+
+    static boolean isMouseEvent(MotionEvent e) {
+        return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
+    }
+
+    static boolean isTouchEvent(MotionEvent e) {
+        return e.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER;
+    }
+
+    static boolean isActionMove(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_MOVE;
+    }
+
+    static boolean isActionDown(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_DOWN;
+    }
+
+    static boolean isActionUp(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_UP;
+    }
+
+    static boolean isActionPointerUp(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_POINTER_UP;
+    }
+
+    @SuppressWarnings("unused")
+    static boolean isActionPointerDown(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN;
+    }
+
+    static boolean isActionCancel(MotionEvent e) {
+        return e.getActionMasked() == MotionEvent.ACTION_CANCEL;
+    }
+
+    static Point getOrigin(MotionEvent e) {
+        return new Point((int) e.getX(), (int) e.getY());
+    }
+
+    static boolean isPrimaryButtonPressed(MotionEvent e) {
+        return isButtonPressed(e, MotionEvent.BUTTON_PRIMARY);
+    }
+
+    static boolean isSecondaryButtonPressed(MotionEvent e) {
+        return isButtonPressed(e, MotionEvent.BUTTON_SECONDARY);
+    }
+
+    static boolean isTertiaryButtonPressed(MotionEvent e) {
+        return isButtonPressed(e, MotionEvent.BUTTON_TERTIARY);
+    }
+
+    // TODO: Replace with MotionEvent.isButtonPressed once targeting 21 or higher.
+    private static boolean isButtonPressed(MotionEvent e, int button) {
+        if (button == 0) {
+            return false;
+        }
+        return (e.getButtonState() & button) == button;
+    }
+
+    static boolean isShiftKeyPressed(MotionEvent e) {
+        return hasBit(e.getMetaState(), KeyEvent.META_SHIFT_ON);
+    }
+
+    static boolean isCtrlKeyPressed(MotionEvent e) {
+        return hasBit(e.getMetaState(), KeyEvent.META_CTRL_ON);
+    }
+
+    static boolean isAltKeyPressed(MotionEvent e) {
+        return hasBit(e.getMetaState(), KeyEvent.META_ALT_ON);
+    }
+
+    static boolean isTouchpadScroll(MotionEvent e) {
+        // Touchpad inputs are treated as mouse inputs, and when scrolling, there are no buttons
+        // returned.
+        return isMouseEvent(e) && isActionMove(e) && e.getButtonState() == 0;
+    }
+
+    private static boolean hasBit(int metaState, int bit) {
+        return (metaState & bit) != 0;
+    }
+}
diff --git a/androidx/recyclerview/selection/MotionInputHandler.java b/androidx/recyclerview/selection/MotionInputHandler.java
new file mode 100644
index 0000000..1c06302
--- /dev/null
+++ b/androidx/recyclerview/selection/MotionInputHandler.java
@@ -0,0 +1,111 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * Base class for handlers that can be registered w/ {@link GestureRouter}.
+ */
+abstract class MotionInputHandler<K> extends SimpleOnGestureListener {
+
+    protected final SelectionHelper<K> mSelectionHelper;
+
+    private final ItemKeyProvider<K> mKeyProvider;
+    private final FocusCallbacks<K> mFocusCallbacks;
+
+    MotionInputHandler(
+            SelectionHelper<K> selectionHelper,
+            ItemKeyProvider<K> keyProvider,
+            FocusCallbacks<K> focusCallbacks) {
+
+        checkArgument(selectionHelper != null);
+        checkArgument(keyProvider != null);
+        checkArgument(focusCallbacks != null);
+
+        mSelectionHelper = selectionHelper;
+        mKeyProvider = keyProvider;
+        mFocusCallbacks = focusCallbacks;
+    }
+
+    final boolean selectItem(ItemDetails<K> details) {
+        checkArgument(details != null);
+        checkArgument(hasPosition(details));
+        checkArgument(hasSelectionKey(details));
+
+        if (mSelectionHelper.select(details.getSelectionKey())) {
+            mSelectionHelper.anchorRange(details.getPosition());
+        }
+
+        // we set the focus on this doc so it will be the origin for keyboard events or shift+clicks
+        // if there is only a single item selected, otherwise clear focus
+        if (mSelectionHelper.getSelection().size() == 1) {
+            mFocusCallbacks.focusItem(details);
+        } else {
+            mFocusCallbacks.clearFocus();
+        }
+        return true;
+    }
+
+    protected final boolean focusItem(ItemDetails<K> details) {
+        checkArgument(details != null);
+        checkArgument(hasSelectionKey(details));
+
+        mSelectionHelper.clearSelection();
+        mFocusCallbacks.focusItem(details);
+        return true;
+    }
+
+    protected final void extendSelectionRange(ItemDetails<K> details) {
+        checkState(mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED));
+        checkArgument(hasPosition(details));
+        checkArgument(hasSelectionKey(details));
+
+        mSelectionHelper.extendRange(details.getPosition());
+        mFocusCallbacks.focusItem(details);
+    }
+
+    final boolean isRangeExtension(MotionEvent e) {
+        return MotionEvents.isShiftKeyPressed(e)
+                && mSelectionHelper.isRangeActive()
+                // Without full corpus access we can't reliably implement range
+                // as a user can scroll *anywhere* then SHIFT+click.
+                && mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED);
+    }
+
+    boolean shouldClearSelection(MotionEvent e, ItemDetails<K> item) {
+        return !MotionEvents.isCtrlKeyPressed(e)
+                && !item.inSelectionHotspot(e)
+                && !mSelectionHelper.isSelected(item.getSelectionKey());
+    }
+
+    static boolean hasSelectionKey(@Nullable ItemDetails<?> item) {
+        return item != null && item.getSelectionKey() != null;
+    }
+
+    static boolean hasPosition(@Nullable ItemDetails<?> item) {
+        return item != null && item.getPosition() != RecyclerView.NO_POSITION;
+    }
+}
diff --git a/androidx/recyclerview/selection/MouseCallbacks.java b/androidx/recyclerview/selection/MouseCallbacks.java
new file mode 100644
index 0000000..05c47c1
--- /dev/null
+++ b/androidx/recyclerview/selection/MouseCallbacks.java
@@ -0,0 +1,48 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.view.MotionEvent;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class MouseCallbacks {
+
+    static final MouseCallbacks DUMMY = new MouseCallbacks() {
+        @Override
+        public boolean onContextClick(MotionEvent e) {
+            return false;
+        }
+    };
+
+    /**
+     * Called when user performs a context click, usually via mouse pointer
+     * right-click.
+     *
+     * @param e the event associated with the click.
+     * @return true if the event was handled.
+     */
+    public abstract boolean onContextClick(MotionEvent e);
+}
diff --git a/androidx/recyclerview/selection/MouseInputHandler.java b/androidx/recyclerview/selection/MouseInputHandler.java
new file mode 100644
index 0000000..0b4ea2c
--- /dev/null
+++ b/androidx/recyclerview/selection/MouseInputHandler.java
@@ -0,0 +1,223 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+
+/**
+ * A MotionInputHandler that provides the high-level glue for mouse/stylus driven selection. This
+ * class works with {@link RecyclerView}, {@link GestureRouter}, and {@link GestureSelectionHelper}
+ * to provide robust user drive selection support.
+ */
+final class MouseInputHandler<K> extends MotionInputHandler<K> {
+
+    private static final String TAG = "MouseInputDelegate";
+
+    private final ItemDetailsLookup<K> mDetailsLookup;
+    private final MouseCallbacks mMouseCallbacks;
+    private final ActivationCallbacks<K> mActivationCallbacks;
+    private final FocusCallbacks<K> mFocusCallbacks;
+
+    // The event has been handled in onSingleTapUp
+    private boolean mHandledTapUp;
+    // true when the previous event has consumed a right click motion event
+    private boolean mHandledOnDown;
+
+    MouseInputHandler(
+            SelectionHelper<K> selectionHelper,
+            ItemKeyProvider<K> keyProvider,
+            ItemDetailsLookup<K> detailsLookup,
+            MouseCallbacks mouseCallbacks,
+            ActivationCallbacks<K> activationCallbacks,
+            FocusCallbacks<K> focusCallbacks) {
+
+        super(selectionHelper, keyProvider, focusCallbacks);
+
+        checkArgument(detailsLookup != null);
+        checkArgument(mouseCallbacks != null);
+        checkArgument(activationCallbacks != null);
+
+        mDetailsLookup = detailsLookup;
+        mMouseCallbacks = mouseCallbacks;
+        mActivationCallbacks = activationCallbacks;
+        mFocusCallbacks = focusCallbacks;
+    }
+
+    @Override
+    public boolean onDown(MotionEvent e) {
+        if (VERBOSE) Log.v(TAG, "Delegated onDown event.");
+        if ((MotionEvents.isAltKeyPressed(e) && MotionEvents.isPrimaryButtonPressed(e))
+                || MotionEvents.isSecondaryButtonPressed(e)) {
+            mHandledOnDown = true;
+            return onRightClick(e);
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        // Don't scroll content window in response to mouse drag
+        // If it's two-finger trackpad scrolling, we want to scroll
+        return !MotionEvents.isTouchpadScroll(e2);
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        // See b/27377794. Since we don't get a button state back from UP events, we have to
+        // explicitly save this state to know whether something was previously handled by
+        // DOWN events or not.
+        if (mHandledOnDown) {
+            if (VERBOSE) Log.v(TAG, "Ignoring onSingleTapUp, previously handled in onDown.");
+            mHandledOnDown = false;
+            return false;
+        }
+
+        if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+            if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection.");
+            mSelectionHelper.clearSelection();
+            mFocusCallbacks.clearFocus();
+            return false;
+        }
+
+        if (MotionEvents.isTertiaryButtonPressed(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring middle click");
+            return false;
+        }
+
+        if (mSelectionHelper.hasSelection()) {
+            onItemClick(e, mDetailsLookup.getItemDetails(e));
+            mHandledTapUp = true;
+            return true;
+        }
+
+        return false;
+    }
+
+    // tap on an item when there is an existing selection. We could extend
+    // a selection, we could clear selection (then launch)
+    private void onItemClick(MotionEvent e, ItemDetails<K> item) {
+        checkState(mSelectionHelper.hasSelection());
+        checkArgument(item != null);
+
+        if (isRangeExtension(e)) {
+            extendSelectionRange(item);
+        } else {
+            if (shouldClearSelection(e, item)) {
+                mSelectionHelper.clearSelection();
+            }
+            if (mSelectionHelper.isSelected(item.getSelectionKey())) {
+                if (mSelectionHelper.deselect(item.getSelectionKey())) {
+                    mFocusCallbacks.clearFocus();
+                }
+            } else {
+                selectOrFocusItem(item, e);
+            }
+        }
+    }
+
+    @Override
+    public boolean onSingleTapConfirmed(MotionEvent e) {
+        if (mHandledTapUp) {
+            if (VERBOSE) {
+                Log.v(TAG,
+                        "Ignoring onSingleTapConfirmed, previously handled in onSingleTapUp.");
+            }
+            mHandledTapUp = false;
+            return false;
+        }
+
+        if (mSelectionHelper.hasSelection()) {
+            return false;  // should have been handled by onSingleTapUp.
+        }
+
+        if (!mDetailsLookup.overItem(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring Confirmed Tap on non-item.");
+            return false;
+        }
+
+        if (MotionEvents.isTertiaryButtonPressed(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring middle click");
+            return false;
+        }
+
+        @Nullable ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+        if (item == null || !item.hasSelectionKey()) {
+            return false;
+        }
+
+        if (mFocusCallbacks.hasFocusedItem() && MotionEvents.isShiftKeyPressed(e)) {
+            mSelectionHelper.startRange(mFocusCallbacks.getFocusedPosition());
+            mSelectionHelper.extendRange(item.getPosition());
+        } else {
+            selectOrFocusItem(item, e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onDoubleTap(MotionEvent e) {
+        mHandledTapUp = false;
+
+        if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring DoubleTap on non-model-backed item.");
+            return false;
+        }
+
+        if (MotionEvents.isTertiaryButtonPressed(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring middle click");
+            return false;
+        }
+
+        ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+        return (item != null) && mActivationCallbacks.onItemActivated(item, e);
+    }
+
+    private boolean onRightClick(MotionEvent e) {
+        if (mDetailsLookup.overItemWithSelectionKey(e)) {
+            @Nullable ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+            if (item != null && !mSelectionHelper.isSelected(item.getSelectionKey())) {
+                mSelectionHelper.clearSelection();
+                selectItem(item);
+            }
+        }
+
+        // We always delegate final handling of the event,
+        // since the handler might want to show a context menu
+        // in an empty area or some other weirdo view.
+        return mMouseCallbacks.onContextClick(e);
+    }
+
+    private void selectOrFocusItem(ItemDetails<K> item, MotionEvent e) {
+        if (item.inSelectionHotspot(e) || MotionEvents.isCtrlKeyPressed(e)) {
+            selectItem(item);
+        } else {
+            focusItem(item);
+        }
+    }
+}
diff --git a/androidx/recyclerview/selection/MutableSelection.java b/androidx/recyclerview/selection/MutableSelection.java
new file mode 100644
index 0000000..6e11698
--- /dev/null
+++ b/androidx/recyclerview/selection/MutableSelection.java
@@ -0,0 +1,54 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+
+/**
+ * Subclass of Selection exposing public support for mutating the underlying selection data.
+ * This is useful for clients of {@link SelectionHelper} that wish to manipulate
+ * a copy of selection data obtained via {@link SelectionHelper#copySelection(Selection)}.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class MutableSelection<K> extends Selection<K> {
+
+    @Override
+    public boolean add(K key) {
+        return super.add(key);
+    }
+
+    @Override
+    public boolean remove(K key) {
+        return super.remove(key);
+    }
+
+    @Override
+    public void copyFrom(Selection<K> source) {
+        super.copyFrom(source);
+    }
+
+    @Override
+    public void clear() {
+        super.clear();
+    }
+}
diff --git a/androidx/recyclerview/selection/Range.java b/androidx/recyclerview/selection/Range.java
new file mode 100644
index 0000000..632e436
--- /dev/null
+++ b/androidx/recyclerview/selection/Range.java
@@ -0,0 +1,186 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+
+import android.support.annotation.IntDef;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class providing support for managing range selections.
+ */
+final class Range {
+
+    static final int TYPE_PRIMARY = 0;
+
+    /**
+     * "Provisional" selection represents a overlay on the primary selection. A provisional
+     * selection maybe be eventually added to the primary selection, or it may be abandoned.
+     *
+     * <p>E.g. BandSelectionHelper creates a provisional selection while a user is actively
+     * selecting items with a band. GestureSelectionHelper creates a provisional selection
+     * while a user is active selecting via gesture.
+     *
+     * <p>Provisionally selected items are considered to be selected in
+     * {@link Selection#contains(String)} and related methods. A provisional may be abandoned or
+     * merged into the promary selection.
+     *
+     * <p>A provisional selection may intersect with the primary selection, however clearing the
+     * provisional selection will not affect the primary selection where the two may intersect.
+     */
+    static final int TYPE_PROVISIONAL = 1;
+    @IntDef({
+            TYPE_PRIMARY,
+            TYPE_PROVISIONAL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface RangeType {}
+
+    private static final String TAG = "Range";
+
+    private final Callbacks mCallbacks;
+    private final int mBegin;
+    private int mEnd = NO_POSITION;
+
+    /**
+     * Creates a new range anchored at {@code position}.
+     *
+     * @param position
+     * @param callbacks
+     */
+    Range(int position, Callbacks callbacks) {
+        mBegin = position;
+        mCallbacks = callbacks;
+        if (DEBUG) Log.d(TAG, "Creating new Range anchored @ " + position);
+    }
+
+    void extendRange(int position, @RangeType int type) {
+        checkArgument(position != NO_POSITION, "Position cannot be NO_POSITION.");
+
+        if (mEnd == NO_POSITION || mEnd == mBegin) {
+            // Reset mEnd so it can be established in establishRange.
+            mEnd = NO_POSITION;
+            establishRange(position, type);
+        } else {
+            reviseRange(position, type);
+        }
+    }
+
+    private void establishRange(int position, @RangeType int type) {
+        checkArgument(mEnd == NO_POSITION, "End has already been set.");
+
+        mEnd = position;
+
+        if (position > mBegin) {
+            if (DEBUG) log(type, "Establishing initial range at @ " + position);
+            updateRange(mBegin + 1, position, true, type);
+        } else if (position < mBegin) {
+            if (DEBUG) log(type, "Establishing initial range at @ " + position);
+            updateRange(position, mBegin - 1, true, type);
+        }
+    }
+
+    private void reviseRange(int position, @RangeType int type) {
+        checkArgument(mEnd != NO_POSITION, "End must already be set.");
+        checkArgument(mBegin != mEnd, "Beging and end point to same position.");
+
+        if (position == mEnd) {
+            if (DEBUG) log(type, "Ignoring no-op revision for range @ " + position);
+        }
+
+        if (mEnd > mBegin) {
+            reviseAscending(position, type);
+        } else if (mEnd < mBegin) {
+            reviseDescending(position, type);
+        }
+        // the "else" case is covered by checkState at beginning of method.
+
+        mEnd = position;
+    }
+
+    /**
+     * Updates an existing ascending selection.
+     */
+    private void reviseAscending(int position, @RangeType int type) {
+        if (DEBUG) log(type, "*ascending* Revising range @ " + position);
+
+        if (position < mEnd) {
+            if (position < mBegin) {
+                updateRange(mBegin + 1, mEnd, false, type);
+                updateRange(position, mBegin - 1, true, type);
+            } else {
+                updateRange(position + 1, mEnd, false, type);
+            }
+        } else if (position > mEnd) {   // Extending the range...
+            updateRange(mEnd + 1, position, true, type);
+        }
+    }
+
+    private void reviseDescending(int position, @RangeType int type) {
+        if (DEBUG) log(type, "*descending* Revising range @ " + position);
+
+        if (position > mEnd) {
+            if (position > mBegin) {
+                updateRange(mEnd, mBegin - 1, false, type);
+                updateRange(mBegin + 1, position, true, type);
+            } else {
+                updateRange(mEnd, position - 1, false, type);
+            }
+        } else if (position < mEnd) {   // Extending the range...
+            updateRange(position, mEnd - 1, true, type);
+        }
+    }
+
+    /**
+     * Try to set selection state for all elements in range. Not that callbacks can cancel
+     * selection of specific items, so some or even all items may not reflect the desired state
+     * after the update is complete.
+     *
+     * @param begin    Adapter position for range start (inclusive).
+     * @param end      Adapter position for range end (inclusive).
+     * @param selected New selection state.
+     */
+    private void updateRange(
+            int begin, int end, boolean selected, @RangeType int type) {
+        mCallbacks.updateForRange(begin, end, selected, type);
+    }
+
+    @Override
+    public String toString() {
+        return "Range{begin=" + mBegin + ", end=" + mEnd + "}";
+    }
+
+    private void log(@RangeType int type, String message) {
+        String opType = type == TYPE_PRIMARY ? "PRIMARY" : "PROVISIONAL";
+        Log.d(TAG, String.valueOf(this) + ": " + message + " (" + opType + ")");
+    }
+
+    /*
+     * @see {@link DefaultSelectionHelper#updateForRange(int, int , boolean, int)}.
+     */
+    abstract static class Callbacks {
+        abstract void updateForRange(
+                int begin, int end, boolean selected, @RangeType int type);
+    }
+}
diff --git a/androidx/recyclerview/selection/Selection.java b/androidx/recyclerview/selection/Selection.java
new file mode 100644
index 0000000..a622530
--- /dev/null
+++ b/androidx/recyclerview/selection/Selection.java
@@ -0,0 +1,259 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Object representing the current selection and provisional selection. Provides read only public
+ * access, and private write access.
+ * <p>
+ * This class tracks selected items by managing two sets:
+ *
+ * <li>primary selection
+ *
+ * Primary selection consists of items tapped by a user or by lassoed by band select operation.
+ *
+ * <li>provisional selection
+ *
+ * Provisional selections are selections which have been temporarily created
+ * by an in-progress band select or gesture selection. Once the user releases the mouse button
+ * or lifts their finger the corresponding provisional selection should be converted into
+ * primary selection.
+ *
+ * <p>The total selection is the combination of
+ * both the core selection and the provisional selection. Tracking both separately is necessary to
+ * ensure that items in the core selection are not "erased" from the core selection when they
+ * are temporarily included in a secondary selection (like band selection).
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class Selection<K> implements Iterable<K> {
+
+    // NOTE: Not currently private as DefaultSelectionHelper directly manipulates values.
+    final Set<K> mSelection;
+    final Set<K> mProvisionalSelection;
+
+    Selection() {
+        mSelection = new HashSet<>();
+        mProvisionalSelection = new HashSet<>();
+    }
+
+    /**
+     * Used by {@link SelectionStorage} when restoring selection.
+     */
+    Selection(Set<K> selection) {
+        mSelection = selection;
+        mProvisionalSelection = new HashSet<>();
+    }
+
+    /**
+     * @param key
+     * @return true if the position is currently selected.
+     */
+    public boolean contains(@Nullable K key) {
+        return mSelection.contains(key) || mProvisionalSelection.contains(key);
+    }
+
+    /**
+     * Returns an {@link Iterator} that iterators over the selection, *excluding*
+     * any provisional selection.
+     *
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterator<K> iterator() {
+        return mSelection.iterator();
+    }
+
+    /**
+     * @return size of the selection including both final and provisional selected items.
+     */
+    public int size() {
+        return mSelection.size() + mProvisionalSelection.size();
+    }
+
+    /**
+     * @return true if the selection is empty.
+     */
+    public boolean isEmpty() {
+        return mSelection.isEmpty() && mProvisionalSelection.isEmpty();
+    }
+
+    /**
+     * Sets the provisional selection, which is a temporary selection that can be saved,
+     * canceled, or adjusted at a later time. When a new provision selection is applied, the old
+     * one (if it exists) is abandoned.
+     * @return Map of ids added or removed. Added ids have a value of true, removed are false.
+     */
+    Map<K, Boolean> setProvisionalSelection(Set<K> newSelection) {
+        Map<K, Boolean> delta = new HashMap<>();
+
+        for (K key: mProvisionalSelection) {
+            // Mark each item that used to be in the provisional selection
+            // but is not in the new provisional selection.
+            if (!newSelection.contains(key) && !mSelection.contains(key)) {
+                delta.put(key, false);
+            }
+        }
+
+        for (K key: mSelection) {
+            // Mark each item that used to be in the selection but is unsaved and not in the new
+            // provisional selection.
+            if (!newSelection.contains(key)) {
+                delta.put(key, false);
+            }
+        }
+
+        for (K key: newSelection) {
+            // Mark each item that was not previously in the selection but is in the new
+            // provisional selection.
+            if (!mSelection.contains(key) && !mProvisionalSelection.contains(key)) {
+                delta.put(key, true);
+            }
+        }
+
+        // Now, iterate through the changes and actually add/remove them to/from the current
+        // selection. This could not be done in the previous loops because changing the size of
+        // the selection mid-iteration changes iteration order erroneously.
+        for (Map.Entry<K, Boolean> entry: delta.entrySet()) {
+            K key = entry.getKey();
+            if (entry.getValue()) {
+                mProvisionalSelection.add(key);
+            } else {
+                mProvisionalSelection.remove(key);
+            }
+        }
+
+        return delta;
+    }
+
+    /**
+     * Saves the existing provisional selection. Once the provisional selection is saved,
+     * subsequent provisional selections which are different from this existing one cannot
+     * cause items in this existing provisional selection to become deselected.
+     */
+    @VisibleForTesting
+    protected void mergeProvisionalSelection() {
+        mSelection.addAll(mProvisionalSelection);
+        mProvisionalSelection.clear();
+    }
+
+    /**
+     * Abandons the existing provisional selection so that all items provisionally selected are
+     * now deselected.
+     */
+    @VisibleForTesting
+    void clearProvisionalSelection() {
+        mProvisionalSelection.clear();
+    }
+
+    /**
+     * Adds a new item to the primary selection.
+     *
+     * @return true if the operation resulted in a modification to the selection.
+     */
+    boolean add(K key) {
+        if (mSelection.contains(key)) {
+            return false;
+        }
+
+        mSelection.add(key);
+        return true;
+    }
+
+    /**
+     * Removes an item from the primary selection.
+     *
+     * @return true if the operation resulted in a modification to the selection.
+     */
+    boolean remove(K key) {
+        if (!mSelection.contains(key)) {
+            return false;
+        }
+
+        mSelection.remove(key);
+        return true;
+    }
+
+    /**
+     * Clears the primary selection. The provisional selection, if any, is unaffected.
+     */
+    void clear() {
+        mSelection.clear();
+    }
+
+    /**
+     * Clones primary and provisional selection from supplied {@link Selection}.
+     * Does not copy active range data.
+     */
+    void copyFrom(Selection<K> source) {
+        mSelection.clear();
+        mSelection.addAll(source.mSelection);
+
+        mProvisionalSelection.clear();
+        mProvisionalSelection.addAll(source.mProvisionalSelection);
+    }
+
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "size=0, items=[]";
+        }
+
+        StringBuilder buffer = new StringBuilder(size() * 28);
+        buffer.append("Selection{")
+            .append("primary{size=" + mSelection.size())
+            .append(", entries=" + mSelection)
+            .append("}, provisional{size=" + mProvisionalSelection.size())
+            .append(", entries=" + mProvisionalSelection)
+            .append("}}");
+        return buffer.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return mSelection.hashCode() ^ mProvisionalSelection.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        return other instanceof Selection && isEqualTo((Selection) other);
+    }
+
+    private boolean isEqualTo(Selection other) {
+        return mSelection.equals(other.mSelection)
+                && mProvisionalSelection.equals(other.mProvisionalSelection);
+    }
+}
diff --git a/androidx/recyclerview/selection/SelectionHelper.java b/androidx/recyclerview/selection/SelectionHelper.java
new file mode 100644
index 0000000..276f903
--- /dev/null
+++ b/androidx/recyclerview/selection/SelectionHelper.java
@@ -0,0 +1,251 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+import java.util.Set;
+
+/**
+ * SelectionManager provides support for managing selection within a RecyclerView instance.
+ *
+ * @see DefaultSelectionHelper for details on instantiation.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class SelectionHelper<K> {
+
+    /**
+     * This value is included in the payload when SelectionHelper implementations
+     * notify RecyclerView of changes. Clients can look for this in
+     * {@code onBindViewHolder} to know if the bind event is occurring in response
+     * to a selection state change.
+     */
+    public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
+
+    /**
+     * Adds {@code observer} to be notified when changes to selection occur.
+     * This method allows observers to closely track changes to selection
+     * avoiding the need to poll selection at performance critical points.
+     */
+    public abstract void addObserver(SelectionObserver observer);
+
+    /** @return true if has a selection */
+    public abstract boolean hasSelection();
+
+    /**
+     * Returns a Selection object that provides a live view on the current selection.
+     *
+     * @return The current selection.
+     * @see #copySelection(Selection) on how to get a snapshot
+     * of the selection that will not reflect future changes
+     * to selection.
+     */
+    public abstract Selection getSelection();
+
+    /**
+     * Updates {@code dest} to reflect the current selection.
+     */
+    public abstract void copySelection(Selection dest);
+
+    /**
+     * @return true if the item specified by its id is selected. Shorthand for
+     * {@code getSelection().contains(K)}.
+     */
+    public abstract boolean isSelected(@Nullable K key);
+
+    /**
+     * Restores the selected state of specified items. Used in cases such as restore the selection
+     * after rotation etc. Provisional selection, being provisional 'n all, isn't restored.
+     *
+     * <p>This affords clients the ability to restore selection from selection saved
+     * in Activity state. See {@link android.app.Activity#onCreate(Bundle)}.
+     *
+     * @param savedSelection selection being restored.
+     */
+    public abstract void restoreSelection(Selection savedSelection);
+
+    abstract void onDataSetChanged();
+
+    /**
+     * Clears both primary selection and provisional selection.
+     *
+     * @return true if anything changed.
+     */
+    public abstract boolean clear();
+
+    /**
+     * Clears the selection and notifies (if something changes).
+     */
+    public abstract void clearSelection();
+
+    /**
+     * Sets the selected state of the specified items. Note that the callback will NOT
+     * be consulted to see if an item can be selected.
+     */
+    public abstract boolean setItemsSelected(Iterable<K> keys, boolean selected);
+
+    /**
+     * Attempts to select an item.
+     *
+     * @return true if the item was selected. False if the item was not selected, or was
+     * was already selected prior to the method being called.
+     */
+    public abstract boolean select(K key);
+
+    /**
+     * Attempts to deselect an item.
+     *
+     * @return true if the item was deselected. False if the item was not deselected, or was
+     * was already deselected prior to the method being called.
+     */
+    public abstract boolean deselect(K key);
+
+    /**
+     * Selects the item at position and establishes the "anchor" for a range selection,
+     * replacing any existing range anchor.
+     *
+     * @param position The anchor position for the selection range.
+     */
+    public abstract void startRange(int position);
+
+    /**
+     * Sets the end point for the active range selection.
+     *
+     * <p>This function should only be called when a range selection is active
+     * (see {@link #isRangeActive()}. Items in the range [anchor, end] will be
+     * selected.
+     *
+     * @param position  The new end position for the selection range.
+     * @throws IllegalStateException if a range selection is not active. Range selection
+     *         must have been started by a call to {@link #startRange(int)}.
+     */
+    public abstract void extendRange(int position);
+
+    /**
+     * Stops an in-progress range selection. All selection done with
+     * {@link #extendProvisionalRange(int)} will be lost if
+     * {@link Selection#mergeProvisionalSelection()} is not called beforehand.
+     */
+    public abstract void endRange();
+
+    /**
+     * @return Whether or not there is a current range selection active.
+     */
+    public abstract boolean isRangeActive();
+
+    /**
+     * Establishes the "anchor" at which a selection range begins. This "anchor" is consulted
+     * when determining how to extend, and modify selection ranges. Calling this when a
+     * range selection is active will reset the range selection.
+     *
+     * @param position the anchor position. Must already be selected.
+     */
+    protected abstract void anchorRange(int position);
+
+    /**
+     * @param position
+     */
+    // TODO: This is smelly. Maybe this type of logic needs to move into range selection,
+    // then selection manager can have a startProvisionalRange and startRange. Or
+    // maybe ranges always start life as provisional.
+    protected abstract void extendProvisionalRange(int position);
+
+    /**
+     * Sets the provisional selection, replacing any existing selection.
+     * @param newSelection
+     */
+    public abstract void setProvisionalSelection(Set<K> newSelection);
+
+    /** Clears any existing provisional selection */
+    public abstract void clearProvisionalSelection();
+
+    /**
+     * Converts the provisional selection into primary selection, then clears
+     * provisional selection.
+     */
+    public abstract void mergeProvisionalSelection();
+
+    /**
+     * Observer interface providing access to information about Selection state changes.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public abstract static class SelectionObserver<K> {
+
+        /**
+         * Called when state of an item has been changed.
+         */
+        public void onItemStateChanged(K key, boolean selected) {
+        }
+
+        /**
+         * Called when the underlying data set has change. After this method is called
+         * the selection manager will attempt traverse the existing selection,
+         * calling {@link #onItemStateChanged(K, boolean)} for each selected item,
+         * and deselecting any items that cannot be selected given the updated dataset.
+         */
+        public void onSelectionReset() {
+        }
+
+        /**
+         * Called immediately after completion of any set of changes, excluding
+         * those resulting in calls to {@link #onSelectionReset()} and
+         * {@link #onSelectionRestored()}.
+         */
+        public void onSelectionChanged() {
+        }
+
+        /**
+         * Called immediately after selection is restored.
+         * {@link #onItemStateChanged(K, boolean)} will not be called
+         * for individual items in the selection.
+         */
+        public void onSelectionRestored() {
+        }
+    }
+
+    /**
+     * Implement SelectionPredicate to control when items can be selected or unselected.
+     *
+     * @param <K> Selection key type. Usually String or Long.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public abstract static class SelectionPredicate<K> {
+
+        /** @return true if the item at {@code id} can be set to {@code nextState}. */
+        public abstract boolean canSetStateForKey(K key, boolean nextState);
+
+        /** @return true if the item at {@code id} can be set to {@code nextState}. */
+        public abstract boolean canSetStateAtPosition(int position, boolean nextState);
+
+        /** @return true if more than a single item can be selected. */
+        public abstract boolean canSelectMultiple();
+    }
+}
diff --git a/androidx/recyclerview/selection/SelectionHelperBuilder.java b/androidx/recyclerview/selection/SelectionHelperBuilder.java
new file mode 100644
index 0000000..abdefaf
--- /dev/null
+++ b/androidx/recyclerview/selection/SelectionHelperBuilder.java
@@ -0,0 +1,342 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.content.Context;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Builder class for assembling selection support. Example usage:
+ *
+ * <p><pre>SelectionHelperBuilder selSupport = new SelectionHelperBuilder(
+        mRecView, new DemoStableIdProvider(mAdapter), detailsLookup);
+
+ // By default multi-select is supported.
+ SelectionHelper selHelper = selSupport
+       .build();
+
+ // This configuration support single selection for any element.
+ SelectionHelper selHelper = selSupport
+       .withSelectionPredicate(SelectionHelper.SelectionPredicate.SINGLE_ANYTHING)
+       .build();
+
+ // Lazily bind SelectionHelper. Allows us to defer initialization of the
+ // SelectionHelper dependency until after the adapter is created.
+ mAdapter.bindSelectionHelper(selHelper);
+
+ * </pre></p>
+ *
+ * @see SelectionStorage for important deatils on retaining selection across Activity
+ * lifecycle events.
+ *
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class SelectionHelperBuilder<K> {
+
+    private final RecyclerView mRecView;
+    private final RecyclerView.Adapter<?> mAdapter;
+    private final Context mContext;
+
+    // Content lock provides a mechanism to block content reload while selection
+    // activities are active. If using a loader to load content, route
+    // the call through the content lock using ContentLock#runWhenUnlocked.
+    // This is especially useful when listening on content change notification.
+    private final ContentLock mLock = new ContentLock();
+
+    private SelectionPredicate<K> mSelectionPredicate = SelectionPredicates.selectAnything();
+    private ItemKeyProvider<K> mKeyProvider;
+    private ItemDetailsLookup<K> mDetailsLookup;
+
+    private ActivationCallbacks<K> mActivationCallbacks = ActivationCallbacks.dummy();
+    private FocusCallbacks<K> mFocusCallbacks = FocusCallbacks.dummy();
+    private TouchCallbacks mTouchCallbacks = TouchCallbacks.DUMMY;
+    private MouseCallbacks mMouseCallbacks = MouseCallbacks.DUMMY;
+
+    private BandPredicate mBandPredicate;
+    private int mBandOverlayId = R.drawable.selection_band_overlay;
+
+    private int[] mGestureToolTypes = new int[] {
+        MotionEvent.TOOL_TYPE_FINGER,
+        MotionEvent.TOOL_TYPE_UNKNOWN
+    };
+
+    private int[] mBandToolTypes = new int[] {
+        MotionEvent.TOOL_TYPE_MOUSE,
+        MotionEvent.TOOL_TYPE_STYLUS
+    };
+
+    public SelectionHelperBuilder(
+            RecyclerView recView,
+            ItemKeyProvider<K> keyProvider,
+            ItemDetailsLookup<K> detailsLookup) {
+
+        checkArgument(recView != null);
+
+        mRecView = recView;
+        mContext = recView.getContext();
+        mAdapter = recView.getAdapter();
+
+        checkArgument(mAdapter != null);
+        checkArgument(keyProvider != null);
+        checkArgument(detailsLookup != null);
+
+        mDetailsLookup = detailsLookup;
+        mKeyProvider = keyProvider;
+
+        mBandPredicate = BandPredicate.notDraggable(mRecView, detailsLookup);
+    }
+
+    /**
+     * Install seleciton predicate.
+     * @param predicate
+     * @return
+     */
+    public SelectionHelperBuilder<K> withSelectionPredicate(SelectionPredicate<K> predicate) {
+        checkArgument(predicate != null);
+        mSelectionPredicate = predicate;
+        return this;
+    }
+
+    /**
+     * Add activation callbacks to respond to taps/enter/double-click on items.
+     *
+     * @param callbacks
+     * @return
+     */
+    public SelectionHelperBuilder<K> withActivationCallbacks(ActivationCallbacks<K> callbacks) {
+        checkArgument(callbacks != null);
+        mActivationCallbacks = callbacks;
+        return this;
+    }
+
+    /**
+     * Add focus callbacks to interfact with selection related focus changes.
+     * @param callbacks
+     * @return
+     */
+    public SelectionHelperBuilder<K> withFocusCallbacks(FocusCallbacks<K> callbacks) {
+        checkArgument(callbacks != null);
+        mFocusCallbacks = callbacks;
+        return this;
+    }
+
+    /**
+     * Configures mouse callbacks, replacing defaults.
+     *
+     * @param callbacks
+     * @return
+     */
+    public SelectionHelperBuilder<K> withMouseCallbacks(MouseCallbacks callbacks) {
+        checkArgument(callbacks != null);
+
+        mMouseCallbacks = callbacks;
+        return this;
+    }
+
+    /**
+     * Replaces default touch callbacks.
+     *
+     * @param callbacks
+     * @return
+     */
+    public SelectionHelperBuilder<K> withTouchCallbacks(TouchCallbacks callbacks) {
+        checkArgument(callbacks != null);
+
+        mTouchCallbacks = callbacks;
+        return this;
+    }
+
+    /**
+     * Replaces default gesture tooltypes.
+     * @param toolTypes
+     * @return
+     */
+    public SelectionHelperBuilder<K> withTouchTooltypes(int... toolTypes) {
+        mGestureToolTypes = toolTypes;
+        return this;
+    }
+
+    /**
+     * Replaces default band overlay.
+     *
+     * @param bandOverlayId
+     * @return
+     */
+    public SelectionHelperBuilder<K> withBandOverlay(@DrawableRes int bandOverlayId) {
+        mBandOverlayId = bandOverlayId;
+        return this;
+    }
+
+    /**
+     * Replaces default band predicate.
+     * @param bandPredicate
+     * @return
+     */
+    public SelectionHelperBuilder<K> withBandPredicate(BandPredicate bandPredicate) {
+
+        checkArgument(bandPredicate != null);
+
+        mBandPredicate = bandPredicate;
+        return this;
+    }
+
+    /**
+     * Replaces default band tools types.
+     * @param toolTypes
+     * @return
+     */
+    public SelectionHelperBuilder<K> withBandTooltypes(int... toolTypes) {
+        mBandToolTypes = toolTypes;
+        return this;
+    }
+
+    /**
+     * Prepares selection support and returns the corresponding SelectionHelper.
+     *
+     * @return
+     */
+    public SelectionHelper<K> build() {
+
+        SelectionHelper<K> selectionHelper =
+                new DefaultSelectionHelper<>(mKeyProvider, mSelectionPredicate);
+
+        // Event glue between RecyclerView and SelectionHelper keeps the classes separate
+        // so that a SelectionHelper can be shared across RecyclerView instances that
+        // represent the same data in different ways.
+        EventBridge.install(mAdapter, selectionHelper, mKeyProvider);
+
+        AutoScroller scroller = new ViewAutoScroller(ViewAutoScroller.createScrollHost(mRecView));
+
+        // Setup basic input handling, with the touch handler as the default consumer
+        // of events. If mouse handling is configured as well, the mouse input
+        // related handlers will intercept mouse input events.
+
+        // GestureRouter is responsible for routing GestureDetector events
+        // to tool-type specific handlers.
+        GestureRouter<MotionInputHandler> gestureRouter = new GestureRouter<>();
+        GestureDetector gestureDetector = new GestureDetector(mContext, gestureRouter);
+
+        // TouchEventRouter takes its name from RecyclerView#OnItemTouchListener.
+        // Despite "Touch" being in the name, it receives events for all types of tools.
+        // This class is responsible for routing events to tool-type specific handlers,
+        // and if not handled by a handler, on to a GestureDetector for analysis.
+        TouchEventRouter eventRouter = new TouchEventRouter(gestureDetector);
+
+        // GestureSelectionHelper provides logic that interprets a combination
+        // of motions and gestures in order to provide gesture driven selection support
+        // when used in conjunction with RecyclerView.
+        final GestureSelectionHelper gestureHelper =
+                GestureSelectionHelper.create(selectionHelper, mRecView, scroller, mLock);
+
+        // Finally hook the framework up to listening to recycle view events.
+        mRecView.addOnItemTouchListener(eventRouter);
+
+        // But before you move on, there's more work to do. Event plumbing has been
+        // installed, but we haven't registered any of our helpers or callbacks.
+        // Helpers contain predefined logic converting events into selection related events.
+        // Callbacks provide authors the ability to reponspond to other types of
+        // events (like "active" a tapped item). This is broken up into two main
+        // suites, one for "touch" and one for "mouse", though both can and should (usually)
+        // be configued to handle other types of input (to satisfy user expectation).);
+
+        // Provides high level glue for binding touch events
+        // and gestures to selection framework.
+        TouchInputHandler<K> touchHandler = new TouchInputHandler<K>(
+                selectionHelper,
+                mKeyProvider,
+                mDetailsLookup,
+                mSelectionPredicate,
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mSelectionPredicate.canSelectMultiple()) {
+                            gestureHelper.start();
+                        }
+                    }
+                },
+                mTouchCallbacks,
+                mActivationCallbacks,
+                mFocusCallbacks,
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+                    }
+                });
+
+        for (int toolType : mGestureToolTypes) {
+            gestureRouter.register(toolType, touchHandler);
+            eventRouter.register(toolType, gestureHelper);
+        }
+
+        // Provides high level glue for binding mouse/stylus events and gestures
+        // to selection framework.
+        MouseInputHandler<K> mouseHandler = new MouseInputHandler<>(
+                selectionHelper,
+                mKeyProvider,
+                mDetailsLookup,
+                mMouseCallbacks,
+                mActivationCallbacks,
+                mFocusCallbacks);
+
+        for (int toolType : mBandToolTypes) {
+            gestureRouter.register(toolType, mouseHandler);
+        }
+
+        // Band selection not supported in single select mode, or when key access
+        // is limited to anything less than the entire corpus.
+        // TODO: Since we cach grid info from laid out items, we could cache key too.
+        // Then we couldn't have to limit to CORPUS access.
+        if (mKeyProvider.hasAccess(ItemKeyProvider.SCOPE_MAPPED)
+                && mSelectionPredicate.canSelectMultiple()) {
+            // BandSelectionHelper provides support for band selection on-top of a RecyclerView
+            // instance. Given the recycling nature of RecyclerView BandSelectionController
+            // necessarily models and caches list/grid information as the user's pointer
+            // interacts with the item in the RecyclerView. Selectable items that intersect
+            // with the band, both on and off screen, are selected.
+            BandSelectionHelper bandHelper = BandSelectionHelper.create(
+                    mRecView,
+                    scroller,
+                    mBandOverlayId,
+                    mKeyProvider,
+                    selectionHelper,
+                    mSelectionPredicate,
+                    mBandPredicate,
+                    mFocusCallbacks,
+                    mLock);
+
+            for (int toolType : mBandToolTypes) {
+                eventRouter.register(toolType, bandHelper);
+            }
+        }
+
+        return selectionHelper;
+    }
+}
diff --git a/androidx/recyclerview/selection/SelectionPredicates.java b/androidx/recyclerview/selection/SelectionPredicates.java
new file mode 100644
index 0000000..26253d9
--- /dev/null
+++ b/androidx/recyclerview/selection/SelectionPredicates.java
@@ -0,0 +1,84 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * Utility class for creating SelectionPredicate instances.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class SelectionPredicates {
+
+    private SelectionPredicates() {}
+
+    /**
+     * Returns a selection predicate that allows multiples items to be selected, without
+     * any restrictions on which items can be selected.
+     * @param <K>
+     * @return
+     */
+    public static <K> SelectionPredicate<K> selectAnything() {
+        return new SelectionPredicate<K>() {
+            @Override
+            public boolean canSetStateForKey(K key, boolean nextState) {
+                return true;
+            }
+
+            @Override
+            public boolean canSetStateAtPosition(int position, boolean nextState) {
+                return true;
+            }
+
+            @Override
+            public boolean canSelectMultiple() {
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Returns a selection predicate that allows a single item to be selected, without
+     * any restrictions on which item can be selected.
+     * @param <K>
+     * @return
+     */
+    public static <K> SelectionPredicate<K> selectSingleAnything() {
+        return new SelectionPredicate<K>() {
+            @Override
+            public boolean canSetStateForKey(K key, boolean nextState) {
+                return true;
+            }
+
+            @Override
+            public boolean canSetStateAtPosition(int position, boolean nextState) {
+                return true;
+            }
+
+            @Override
+            public boolean canSelectMultiple() {
+                return false;
+            }
+        };
+    }
+}
diff --git a/androidx/recyclerview/selection/SelectionStorage.java b/androidx/recyclerview/selection/SelectionStorage.java
new file mode 100644
index 0000000..81db30f
--- /dev/null
+++ b/androidx/recyclerview/selection/SelectionStorage.java
@@ -0,0 +1,181 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.os.Bundle;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Helper class binding SelectionHelper and Activity lifecycle events facilitating
+ * persistence of selection across activity lifecycle events.
+ *
+ * <p>Usage:<br><pre>
+ void onCreate() {
+    mLifecycleHelper = new SelectionStorage<>(SelectionStorage.TYPE_STRING, mSelectionHelper);
+    if (savedInstanceState != null) {
+        mSelectionStorage.onRestoreInstanceState(savedInstanceState);
+    }
+ }
+ protected void onSaveInstanceState(Bundle outState) {
+     super.onSaveInstanceState(outState);
+     mSelectionStorage.onSaveInstanceState(outState);
+ }
+ </pre>
+ * @param <K> Selection key type. Usually String or Long.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class SelectionStorage<K> {
+
+    @VisibleForTesting
+    static final String EXTRA_SAVED_SELECTION_TYPE = "androidx.recyclerview.selection.type";
+
+    @VisibleForTesting
+    static final String EXTRA_SAVED_SELECTION_ENTRIES = "androidx.recyclerview.selection.entries";
+
+    public static final int TYPE_STRING = 0;
+    public static final int TYPE_LONG = 1;
+    @IntDef({
+            TYPE_STRING,
+            TYPE_LONG
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface KeyType {}
+
+    private final @KeyType int mKeyType;
+    private final SelectionHelper<K> mHelper;
+
+    /**
+     * Creates a new lifecycle helper. {@code keyType}.
+     *
+     * @param keyType
+     * @param helper
+     */
+    public SelectionStorage(@KeyType int keyType, SelectionHelper<K> helper) {
+        checkArgument(
+                keyType == TYPE_STRING || keyType == TYPE_LONG,
+                "Only String and Integer presistence are supported by default.");
+        checkArgument(helper != null);
+
+        mKeyType = keyType;
+        mHelper = helper;
+    }
+
+    /**
+     * Preserves selection, if any.
+     *
+     * @param state
+     */
+    @SuppressWarnings("unchecked")
+    public void onSaveInstanceState(Bundle state) {
+        MutableSelection<K> sel = new MutableSelection<>();
+        mHelper.copySelection(sel);
+
+        state.putInt(EXTRA_SAVED_SELECTION_TYPE, mKeyType);
+        switch (mKeyType) {
+            case TYPE_STRING:
+                writeStringSelection(state, ((Selection<String>) sel).mSelection);
+                break;
+            case TYPE_LONG:
+                writeLongSelection(state, ((Selection<Long>) sel).mSelection);
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported key type: " + mKeyType);
+        }
+    }
+
+    /**
+     * Restores selection from previously saved state.
+     *
+     * @param state
+     */
+    public void onRestoreInstanceState(@Nullable Bundle state) {
+        if (state == null) {
+            return;
+        }
+
+        int keyType = state.getInt(EXTRA_SAVED_SELECTION_TYPE, -1);
+        switch(keyType) {
+            case TYPE_STRING:
+                Selection<String> stringSel = readStringSelection(state);
+                if (stringSel != null && !stringSel.isEmpty()) {
+                    mHelper.restoreSelection(stringSel);
+                }
+                break;
+            case TYPE_LONG:
+                Selection<Long> longSel = readLongSelection(state);
+                if (longSel != null && !longSel.isEmpty()) {
+                    mHelper.restoreSelection(longSel);
+                }
+                break;
+            default:
+                throw new UnsupportedOperationException("Unsupported selection key type.");
+        }
+    }
+
+    private @Nullable Selection<String> readStringSelection(@Nullable Bundle state) {
+        @Nullable ArrayList<String> stored =
+                state.getStringArrayList(EXTRA_SAVED_SELECTION_ENTRIES);
+        if (stored == null) {
+            return null;
+        }
+
+        Selection<String> selection = new Selection<>();
+        selection.mSelection.addAll(stored);
+        return selection;
+    }
+
+    private @Nullable Selection<Long> readLongSelection(@Nullable Bundle state) {
+        @Nullable long[] stored = state.getLongArray(EXTRA_SAVED_SELECTION_ENTRIES);
+        if (stored == null) {
+            return null;
+        }
+
+        Selection<Long> selection = new Selection<>();
+        for (long key : stored) {
+            selection.mSelection.add(key);
+        }
+        return selection;
+    }
+
+    private void writeStringSelection(Bundle state, Set<String> selected) {
+        ArrayList<String> value = new ArrayList<>(selected.size());
+        value.addAll(selected);
+        state.putStringArrayList(EXTRA_SAVED_SELECTION_ENTRIES, value);
+    }
+
+    private void writeLongSelection(Bundle state, Set<Long> selected) {
+        long[] value = new long[selected.size()];
+        int i = 0;
+        for (Long key : selected) {
+            value[i++] = key;
+        }
+        state.putLongArray(EXTRA_SAVED_SELECTION_ENTRIES, value);
+    }
+}
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/androidx/recyclerview/selection/Shared.java
similarity index 64%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to androidx/recyclerview/selection/Shared.java
index e28e1b3..3b79120 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/androidx/recyclerview/selection/Shared.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.telephony.ims.feature;
+package androidx.recyclerview.selection;
 
 /**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
+ * Shared constants used in this package.
  */
+final class Shared {
 
-public interface IRcsFeature {
+    static final boolean DEBUG = false;
+    static final boolean VERBOSE = true;
+
+    private Shared() {}
 }
diff --git a/androidx/recyclerview/selection/StableIdKeyProvider.java b/androidx/recyclerview/selection/StableIdKeyProvider.java
new file mode 100644
index 0000000..3dc78ca
--- /dev/null
+++ b/androidx/recyclerview/selection/StableIdKeyProvider.java
@@ -0,0 +1,99 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnChildAttachStateChangeListener;
+import android.util.SparseArray;
+import android.view.View;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * ItemKeyProvider that provides stable ids by way of cached RecyclerView.Adapter stable ids.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public final class StableIdKeyProvider extends ItemKeyProvider<Long> {
+
+    private final SparseArray<Long> mPositionToKey = new SparseArray<>();
+    private final Map<Long, Integer> mKeyToPosition = new HashMap<Long, Integer>();
+    private final RecyclerView mRecView;
+
+    public StableIdKeyProvider(RecyclerView recView) {
+
+        // Since this provide is based on stable ids based on whats laid out in the window
+        // we can only satisfy "window" scope key access.
+        super(SCOPE_CACHED);
+
+        mRecView = recView;
+
+        mRecView.addOnChildAttachStateChangeListener(
+                new OnChildAttachStateChangeListener() {
+                    @Override
+                    public void onChildViewAttachedToWindow(View view) {
+                        onAttached(view);
+                    }
+
+                    @Override
+                    public void onChildViewDetachedFromWindow(View view) {
+                        onDetached(view);
+                    }
+                }
+        );
+
+    }
+
+    private void onAttached(View view) {
+        RecyclerView.ViewHolder holder = mRecView.findContainingViewHolder(view);
+        int position = holder.getAdapterPosition();
+        long id = holder.getItemId();
+        if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
+            mPositionToKey.put(position, id);
+            mKeyToPosition.put(id, position);
+        }
+    }
+
+    private void onDetached(View view) {
+        RecyclerView.ViewHolder holder = mRecView.findContainingViewHolder(view);
+        int position = holder.getAdapterPosition();
+        long id = holder.getItemId();
+        if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
+            mPositionToKey.delete(position);
+            mKeyToPosition.remove(id);
+        }
+    }
+
+    @Override
+    public @Nullable Long getKey(int position) {
+        return mPositionToKey.get(position, null);
+    }
+
+    @Override
+    public int getPosition(Long key) {
+        if (mKeyToPosition.containsKey(key)) {
+            return mKeyToPosition.get(key);
+        }
+        return RecyclerView.NO_POSITION;
+    }
+}
diff --git a/androidx/recyclerview/selection/ToolHandlerRegistry.java b/androidx/recyclerview/selection/ToolHandlerRegistry.java
new file mode 100644
index 0000000..c735529
--- /dev/null
+++ b/androidx/recyclerview/selection/ToolHandlerRegistry.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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import android.support.annotation.Nullable;
+import android.view.MotionEvent;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Registry for tool specific event handler.
+ */
+final class ToolHandlerRegistry<T> {
+
+    // Currently there are four known input types. ERASER is the last one, so has the
+    // highest value. UNKNOWN is zero, so we add one. This allows delegates to be
+    // registered by type, and avoid the auto-boxing that would be necessary were we
+    // to store delegates in a Map<Integer, Delegate>.
+    private static final int sNumInputTypes = MotionEvent.TOOL_TYPE_ERASER + 1;
+
+    private final List<T> mHandlers = Arrays.asList(null, null, null, null, null);
+    private final T mDefault;
+
+    ToolHandlerRegistry(T defaultDelegate) {
+        checkArgument(defaultDelegate != null);
+        mDefault = defaultDelegate;
+
+        // Initialize all values to null.
+        for (int i = 0; i < sNumInputTypes; i++) {
+            mHandlers.set(i, null);
+        }
+    }
+
+    /**
+     * @param toolType
+     * @param delegate the delegate, or null to unregister.
+     * @throws IllegalStateException if an tooltype handler is already registered.
+     */
+    void set(int toolType, @Nullable T delegate) {
+        checkArgument(toolType >= 0 && toolType <= MotionEvent.TOOL_TYPE_ERASER);
+        checkState(mHandlers.get(toolType) == null);
+
+        mHandlers.set(toolType, delegate);
+    }
+
+    T get(MotionEvent e) {
+        T d = mHandlers.get(e.getToolType(0));
+        return d != null ? d : mDefault;
+    }
+}
diff --git a/androidx/recyclerview/selection/TouchCallbacks.java b/androidx/recyclerview/selection/TouchCallbacks.java
new file mode 100644
index 0000000..5905392
--- /dev/null
+++ b/androidx/recyclerview/selection/TouchCallbacks.java
@@ -0,0 +1,55 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.RestrictTo;
+import android.view.MotionEvent;
+
+/**
+ * Override methods in this class to connect specialized behaviors of the selection
+ * code to the application environment.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public abstract class TouchCallbacks {
+
+    static final TouchCallbacks DUMMY = new TouchCallbacks() {
+        @Override
+        public boolean onDragInitiated(MotionEvent e) {
+            return false;
+        }
+    };
+
+    /**
+     * Called when a drag is initiated. Touch input handler only considers
+     * a drag to be initiated on long press on an existing selection,
+     * as normal touch and drag events are strongly associated with scrolling of the view.
+     *
+     * <p>Drag will only be initiated when the item under the event is already selected.
+     *
+     * <p>The RecyclerView item at the coordinates of the MotionEvent is not supplied as a parameter
+     * to this method as there may be multiple items selected. Clients can obtain the current
+     * list of selected items from {@link SelectionHelper#copySelection(Selection)}.
+     *
+     * @param e the event associated with the drag.
+     * @return true if the event was handled.
+     */
+    public abstract boolean onDragInitiated(MotionEvent e);
+}
diff --git a/androidx/recyclerview/selection/TouchEventRouter.java b/androidx/recyclerview/selection/TouchEventRouter.java
new file mode 100644
index 0000000..fbbca23
--- /dev/null
+++ b/androidx/recyclerview/selection/TouchEventRouter.java
@@ -0,0 +1,105 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+
+/**
+ * A class responsible for routing MotionEvents to tool-type specific handlers,
+ * and if not handled by a handler, on to a {@link GestureDetector} for further
+ * processing.
+ *
+ * <p>TouchEventRouter takes its name from
+ * {@link RecyclerView#addOnItemTouchListener(OnItemTouchListener)}. Despite "Touch"
+ * being in the name, it receives MotionEvents for all types of tools.
+ */
+final class TouchEventRouter implements OnItemTouchListener {
+
+    private static final String TAG = "TouchEventRouter";
+
+    private final GestureDetector mDetector;
+    private final ToolHandlerRegistry<OnItemTouchListener> mDelegates;
+
+    TouchEventRouter(GestureDetector detector, OnItemTouchListener defaultDelegate) {
+        checkArgument(detector != null);
+        checkArgument(defaultDelegate != null);
+
+        mDetector = detector;
+        mDelegates = new ToolHandlerRegistry<>(defaultDelegate);
+    }
+
+    TouchEventRouter(GestureDetector detector) {
+        this(
+                detector,
+                // Supply a fallback listener does nothing...because the caller
+                // didn't supply a fallback.
+                new OnItemTouchListener() {
+                    @Override
+                    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+                        return false;
+                    }
+
+                    @Override
+                    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+                    }
+
+                    @Override
+                    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+                    }
+                });
+    }
+
+    /**
+     * @param toolType See MotionEvent for details on available types.
+     * @param delegate An {@link OnItemTouchListener} to receive events
+     *     of {@code toolType}.
+     */
+    void register(int toolType, OnItemTouchListener delegate) {
+        checkArgument(delegate != null);
+        mDelegates.set(toolType, delegate);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+        boolean handled = mDelegates.get(e).onInterceptTouchEvent(rv, e);
+
+        // Forward all events to UserInputHandler.
+        // This is necessary since UserInputHandler needs to always see the first DOWN event. Or
+        // else all future UP events will be tossed.
+        handled |= mDetector.onTouchEvent(e);
+
+        return handled;
+    }
+
+    @Override
+    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+        mDelegates.get(e).onTouchEvent(rv, e);
+
+        // Note: even though this event is being handled as part of gestures such as drag and band,
+        // continue forwarding to the GestureDetector. The detector needs to see the entire cluster
+        // of events in order to properly interpret other gestures, such as long press.
+        mDetector.onTouchEvent(e);
+    }
+
+    @Override
+    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {}
+}
diff --git a/androidx/recyclerview/selection/TouchInputHandler.java b/androidx/recyclerview/selection/TouchInputHandler.java
new file mode 100644
index 0000000..e07aeb1
--- /dev/null
+++ b/androidx/recyclerview/selection/TouchInputHandler.java
@@ -0,0 +1,149 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails;
+import androidx.recyclerview.selection.SelectionHelper.SelectionPredicate;
+
+/**
+ * A MotionInputHandler that provides the high-level glue for touch driven selection. This class
+ * works with {@link RecyclerView}, {@link GestureRouter}, and {@link GestureSelectionHelper} to
+ * provide robust user drive selection support.
+ */
+final class TouchInputHandler<K> extends MotionInputHandler<K> {
+
+    private static final String TAG = "TouchInputDelegate";
+    private static final boolean DEBUG = false;
+
+    private final ItemDetailsLookup<K> mDetailsLookup;
+    private final SelectionPredicate<K> mSelectionPredicate;
+    private final ActivationCallbacks<K> mActivationCallbacks;
+    private final TouchCallbacks mTouchCallbacks;
+    private final Runnable mGestureStarter;
+    private final Runnable mHapticPerformer;
+
+    TouchInputHandler(
+            SelectionHelper<K> selectionHelper,
+            ItemKeyProvider<K> keyProvider,
+            ItemDetailsLookup<K> detailsLookup,
+            SelectionPredicate<K> selectionPredicate,
+            Runnable gestureStarter,
+            TouchCallbacks touchCallbacks,
+            ActivationCallbacks<K> activationCallbacks,
+            FocusCallbacks<K> focusCallbacks,
+            Runnable hapticPerformer) {
+
+        super(selectionHelper, keyProvider, focusCallbacks);
+
+        checkArgument(detailsLookup != null);
+        checkArgument(selectionPredicate != null);
+        checkArgument(gestureStarter != null);
+        checkArgument(activationCallbacks != null);
+        checkArgument(touchCallbacks != null);
+        checkArgument(hapticPerformer != null);
+
+        mDetailsLookup = detailsLookup;
+        mSelectionPredicate = selectionPredicate;
+        mGestureStarter = gestureStarter;
+        mActivationCallbacks = activationCallbacks;
+        mTouchCallbacks = touchCallbacks;
+        mHapticPerformer = hapticPerformer;
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+            if (DEBUG) Log.d(TAG, "Tap not associated w/ model item. Clearing selection.");
+            mSelectionHelper.clearSelection();
+            return false;
+        }
+
+        ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+        // Should really not be null at this point, but...
+        if (item == null) {
+            return false;
+        }
+
+        if (mSelectionHelper.hasSelection()) {
+            if (isRangeExtension(e)) {
+                extendSelectionRange(item);
+            } else if (mSelectionHelper.isSelected(item.getSelectionKey())) {
+                mSelectionHelper.deselect(item.getSelectionKey());
+            } else {
+                selectItem(item);
+            }
+
+            return true;
+        }
+
+        // Touch events select if they occur in the selection hotspot,
+        // otherwise they activate.
+        return item.inSelectionHotspot(e)
+                ? selectItem(item)
+                : mActivationCallbacks.onItemActivated(item, e);
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+        if (!mDetailsLookup.overItemWithSelectionKey(e)) {
+            if (DEBUG) Log.d(TAG, "Ignoring LongPress on non-model-backed item.");
+            return;
+        }
+
+        ItemDetails<K> item = mDetailsLookup.getItemDetails(e);
+        // Should really not be null at this point, but...
+        if (item == null) {
+            return;
+        }
+
+        boolean handled = false;
+
+        if (isRangeExtension(e)) {
+            extendSelectionRange(item);
+            handled = true;
+        } else {
+            if (!mSelectionHelper.isSelected(item.getSelectionKey())
+                    && mSelectionPredicate.canSetStateForKey(item.getSelectionKey(), true)) {
+                // If we cannot select it, we didn't apply anchoring - therefore should not
+                // start gesture selection
+                if (selectItem(item)) {
+                    // And finally if the item was selected && we can select multiple
+                    // we kick off gesture selection.
+                    if (mSelectionPredicate.canSelectMultiple()) {
+                        mGestureStarter.run();
+                    }
+                    handled = true;
+                }
+            } else {
+                // We only initiate drag and drop on long press for touch to allow regular
+                // touch-based scrolling
+                mTouchCallbacks.onDragInitiated(e);
+                handled = true;
+            }
+        }
+
+        if (handled) {
+            mHapticPerformer.run();
+        }
+    }
+}
diff --git a/androidx/recyclerview/selection/ViewAutoScroller.java b/androidx/recyclerview/selection/ViewAutoScroller.java
new file mode 100644
index 0000000..d13b0f2
--- /dev/null
+++ b/androidx/recyclerview/selection/ViewAutoScroller.java
@@ -0,0 +1,271 @@
+/*
+ * 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 androidx.recyclerview.selection;
+
+import static android.support.v4.util.Preconditions.checkArgument;
+import static android.support.v4.util.Preconditions.checkState;
+
+import static androidx.recyclerview.selection.Shared.DEBUG;
+import static androidx.recyclerview.selection.Shared.VERBOSE;
+
+import android.graphics.Point;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+/**
+ * Provides auto-scrolling upon request when user's interaction with the application
+ * introduces a natural intent to scroll. Used by BandSelectionHelper and GestureSelectionHelper,
+ * to provide auto scrolling when user is performing selection operations.
+ */
+final class ViewAutoScroller extends AutoScroller {
+
+    private static final String TAG = "ViewAutoScroller";
+
+    // ratio used to calculate the top/bottom hotspot region; used with view height
+    private static final float DEFAULT_SCROLL_THRESHOLD_RATIO = 0.125f;
+    private static final int MAX_SCROLL_STEP = 70;
+
+    private final float mScrollThresholdRatio;
+
+    private final ScrollHost mHost;
+    private final Runnable mRunner;
+
+    private @Nullable Point mOrigin;
+    private @Nullable Point mLastLocation;
+    private boolean mPassedInitialMotionThreshold;
+
+    ViewAutoScroller(ScrollHost scrollHost) {
+        this(scrollHost, DEFAULT_SCROLL_THRESHOLD_RATIO);
+    }
+
+    @VisibleForTesting
+    ViewAutoScroller(ScrollHost scrollHost, float scrollThresholdRatio) {
+
+        checkArgument(scrollHost != null);
+
+        mHost = scrollHost;
+        mScrollThresholdRatio = scrollThresholdRatio;
+
+        mRunner = new Runnable() {
+            @Override
+            public void run() {
+                runScroll();
+            }
+        };
+    }
+
+    @Override
+    protected void reset() {
+        mHost.removeCallback(mRunner);
+        mOrigin = null;
+        mLastLocation = null;
+        mPassedInitialMotionThreshold = false;
+    }
+
+    @Override
+    protected void scroll(Point location) {
+        mLastLocation = location;
+
+        // See #aboveMotionThreshold for details on how we track initial location.
+        if (mOrigin == null) {
+            mOrigin = location;
+            if (VERBOSE) Log.v(TAG, "Origin @ " + mOrigin);
+        }
+
+        if (VERBOSE) Log.v(TAG, "Current location @ " + mLastLocation);
+
+        mHost.runAtNextFrame(mRunner);
+    }
+
+    /**
+     * Attempts to smooth-scroll the view at the given UI frame. Application should be
+     * responsible to do any clean up (such as unsubscribing scrollListeners) after the run has
+     * finished, and re-run this method on the next UI frame if applicable.
+     */
+    private void runScroll() {
+        if (DEBUG) checkState(mLastLocation != null);
+
+        if (VERBOSE) Log.v(TAG, "Running in background using event location @ " + mLastLocation);
+
+        // Compute the number of pixels the pointer's y-coordinate is past the view.
+        // Negative values mean the pointer is at or before the top of the view, and
+        // positive values mean that the pointer is at or after the bottom of the view. Note
+        // that top/bottom threshold is added here so that the view still scrolls when the
+        // pointer are in these buffer pixels.
+        int pixelsPastView = 0;
+
+        final int verticalThreshold = (int) (mHost.getViewHeight()
+                * mScrollThresholdRatio);
+
+        if (mLastLocation.y <= verticalThreshold) {
+            pixelsPastView = mLastLocation.y - verticalThreshold;
+        } else if (mLastLocation.y >= mHost.getViewHeight()
+                - verticalThreshold) {
+            pixelsPastView = mLastLocation.y - mHost.getViewHeight()
+                    + verticalThreshold;
+        }
+
+        if (pixelsPastView == 0) {
+            // If the operation that started the scrolling is no longer inactive, or if it is active
+            // but not at the edge of the view, no scrolling is necessary.
+            return;
+        }
+
+        // We're in one of the endzones. Now determine if there's enough of a difference
+        // from the orgin to take any action. Basically if a user has somehow initiated
+        // selection, but is hovering at or near their initial contact point, we don't
+        // scroll. This avoids a situation where the user initiates selection in an "endzone"
+        // only to have scrolling start automatically.
+        if (!mPassedInitialMotionThreshold && !aboveMotionThreshold(mLastLocation)) {
+            if (VERBOSE) Log.v(TAG, "Ignoring event below motion threshold.");
+            return;
+        }
+        mPassedInitialMotionThreshold = true;
+
+        if (pixelsPastView > verticalThreshold) {
+            pixelsPastView = verticalThreshold;
+        }
+
+        // Compute the number of pixels to scroll, and scroll that many pixels.
+        final int numPixels = computeScrollDistance(pixelsPastView);
+        mHost.scrollBy(numPixels);
+
+        // Replace any existing scheduled jobs with the latest and greatest..
+        mHost.removeCallback(mRunner);
+        mHost.runAtNextFrame(mRunner);
+    }
+
+    private boolean aboveMotionThreshold(Point location) {
+        // We reuse the scroll threshold to calculate a much smaller area
+        // in which we ignore motion initially.
+        int motionThreshold =
+                (int) ((mHost.getViewHeight() * mScrollThresholdRatio)
+                        * (mScrollThresholdRatio * 2));
+        return Math.abs(mOrigin.y - location.y) >= motionThreshold;
+    }
+
+    /**
+     * Computes the number of pixels to scroll based on how far the pointer is past the end
+     * of the region. Roughly based on ItemTouchHelper's algorithm for computing the number of
+     * pixels to scroll when an item is dragged to the end of a view.
+     * @return
+     */
+    @VisibleForTesting
+    int computeScrollDistance(int pixelsPastView) {
+        final int topBottomThreshold =
+                (int) (mHost.getViewHeight() * mScrollThresholdRatio);
+
+        final int direction = (int) Math.signum(pixelsPastView);
+        final int absPastView = Math.abs(pixelsPastView);
+
+        // Calculate the ratio of how far out of the view the pointer currently resides to
+        // the top/bottom scrolling hotspot of the view.
+        final float outOfBoundsRatio = Math.min(
+                1.0f, (float) absPastView / topBottomThreshold);
+        // Interpolate this ratio and use it to compute the maximum scroll that should be
+        // possible for this step.
+        final int cappedScrollStep =
+                (int) (direction * MAX_SCROLL_STEP * smoothOutOfBoundsRatio(outOfBoundsRatio));
+
+        // If the final number of pixels to scroll ends up being 0, the view should still
+        // scroll at least one pixel.
+        return cappedScrollStep != 0 ? cappedScrollStep : direction;
+    }
+
+    /**
+     * Interpolates the given out of bounds ratio on a curve which starts at (0,0) and ends
+     * at (1,1) and quickly approaches 1 near the start of that interval. This ensures that
+     * drags that are at the edge or barely past the edge of the threshold does little to no
+     * scrolling, while drags that are near the edge of the view does a lot of
+     * scrolling. The equation y=x^10 is used, but this could also be tweaked if
+     * needed.
+     * @param ratio A ratio which is in the range [0, 1].
+     * @return A "smoothed" value, also in the range [0, 1].
+     */
+    private float smoothOutOfBoundsRatio(float ratio) {
+        return (float) Math.pow(ratio, 10);
+    }
+
+    /**
+     * Used by to calculate the proper amount of pixels to scroll given time passed
+     * since scroll started, and to properly scroll / proper listener clean up if necessary.
+     *
+     * Callback used by scroller to perform UI tasks, such as scrolling and rerunning at next UI
+     * cycle.
+     */
+    abstract static class ScrollHost {
+        /**
+         * @return height of the view.
+         */
+        abstract int getViewHeight();
+
+        /**
+         * @param dy distance to scroll.
+         */
+        abstract void scrollBy(int dy);
+
+        /**
+         * @param r schedule runnable to be run at next convenient time.
+         */
+        abstract void runAtNextFrame(Runnable r);
+
+        /**
+         * @param r remove runnable from being run.
+         */
+        abstract void removeCallback(Runnable r);
+    }
+
+    public static ScrollHost createScrollHost(final RecyclerView view) {
+        return new RuntimeHost(view);
+    }
+
+    /**
+     * Tracks location of last surface contact as reported by RecyclerView.
+     */
+    private static final class RuntimeHost extends ScrollHost {
+
+        private final RecyclerView mRecView;
+
+        RuntimeHost(RecyclerView recView) {
+            mRecView = recView;
+        }
+
+        @Override
+        void runAtNextFrame(Runnable r) {
+            ViewCompat.postOnAnimation(mRecView, r);
+        }
+
+        @Override
+        void removeCallback(Runnable r) {
+            mRecView.removeCallbacks(r);
+        }
+
+        @Override
+        void scrollBy(int dy) {
+            if (VERBOSE) Log.v(TAG, "Scrolling view by: " + dy);
+            mRecView.scrollBy(0, dy);
+        }
+
+        @Override
+        int getViewHeight() {
+            return mRecView.getHeight();
+        }
+    }
+}
diff --git a/com/android/car/setupwizardlib/CarSetupWizardLayout.java b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
index 0f5b84b..d183897 100644
--- a/com/android/car/setupwizardlib/CarSetupWizardLayout.java
+++ b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
@@ -23,7 +23,6 @@
 import android.view.View;
 import android.widget.Button;
 import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
 
 
 /**
@@ -32,9 +31,15 @@
 public class CarSetupWizardLayout extends LinearLayout {
     private View mBackButton;
 
-    private Button mContinueButton;
-
-    private RelativeLayout mHeader;
+    /* The Primary Continue Button should always be used when there is only a single action that
+     * moves the wizard to the next screen (e.g. Only need a 'Skip' button).
+     *
+     * When there are two actions that can move the wizard to the next screen (e.g. either 'Skip'
+     * or 'Let's Go' are the two options), then the Primary is used for the positive action
+     * while the Secondary is used for the negative action.
+     */
+    private Button mPrimaryContinueButton;
+    private Button mSecondaryContinueButton;
 
     public CarSetupWizardLayout(Context context) {
         this(context, null);
@@ -47,6 +52,7 @@
     public CarSetupWizardLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
+
     /**
      * On initialization, the layout gets all of the custom attributes and initializes
      * the custom views that can be set by the user (e.g. back button, continue button).
@@ -69,15 +75,30 @@
      */
     void init(TypedArray attrArray) {
         boolean showBackButton;
-        boolean showContinueButton;
-        String continueButtonText;
+
+        boolean showPrimaryContinueButton;
+        String primaryContinueButtonText;
+        boolean primaryContinueButtonEnabled;
+
+        boolean showSecondaryContinueButton;
+        String secondaryContinueButtonText;
+        boolean secondaryContinueButtonEnabled;
+
         try {
             showBackButton = attrArray.getBoolean(
                     R.styleable.CarSetupWizardLayout_showBackButton, true);
-            showContinueButton = attrArray.getBoolean(
-                    R.styleable.CarSetupWizardLayout_showContinueButton, true);
-            continueButtonText = attrArray.getString(
-                    R.styleable.CarSetupWizardLayout_continueButtonText);
+            showPrimaryContinueButton = attrArray.getBoolean(
+                    R.styleable.CarSetupWizardLayout_showPrimaryContinueButton, true);
+            primaryContinueButtonText = attrArray.getString(
+                    R.styleable.CarSetupWizardLayout_primaryContinueButtonText);
+            primaryContinueButtonEnabled = attrArray.getBoolean(
+                    R.styleable.CarSetupWizardLayout_primaryContinueButtonEnabled, true);
+            showSecondaryContinueButton = attrArray.getBoolean(
+                    R.styleable.CarSetupWizardLayout_showSecondaryContinueButton, false);
+            secondaryContinueButtonText = attrArray.getString(
+                    R.styleable.CarSetupWizardLayout_secondaryContinueButtonText);
+            secondaryContinueButtonEnabled = attrArray.getBoolean(
+                    R.styleable.CarSetupWizardLayout_secondaryContinueButtonEnabled, true);
         } finally {
             attrArray.recycle();
         }
@@ -85,44 +106,38 @@
         LayoutInflater inflater = LayoutInflater.from(getContext());
         inflater.inflate(R.layout.car_setup_wizard_layout, this);
 
-        mHeader = findViewById(R.id.header);
-
         // Set the back button visibility based on the custom attribute.
         mBackButton = findViewById(R.id.back_button);
         if (!showBackButton) {
-            setBackButtonVisibility(View.GONE);
+            setBackButtonVisible(false);
         }
 
-        // Set the continue button visibility and text based on the custom attributes.
-        mContinueButton = findViewById(R.id.continue_button);
-        if (showContinueButton) {
-            setContinueButtonText(continueButtonText);
+        // Set the primary continue button visibility and text based on the custom attributes.
+        mPrimaryContinueButton = findViewById(R.id.primary_continue_button);
+        if (showPrimaryContinueButton) {
+            setPrimaryContinueButtonText(primaryContinueButtonText);
+            setPrimaryContinueButtonEnabled(primaryContinueButtonEnabled);
         } else {
-            setContinueButtonVisibility(View.GONE);
+            setPrimaryContinueButtonVisible(false);
+        }
+
+        // Set the secondary continue button visibility and text based on the custom attributes.
+        mSecondaryContinueButton = findViewById(R.id.secondary_continue_button);
+        if (showSecondaryContinueButton) {
+            setSecondaryContinueButtonText(secondaryContinueButtonText);
+            setSecondaryContinueButtonEnabled(secondaryContinueButtonEnabled);
+        } else {
+            setSecondaryContinueButtonVisible(false);
         }
 
         // TODO: Handle loading bar logic
     }
 
     /**
-     * Set the back button visibility to the given visibility.
+     * Set a given button's visibility.
      */
-    public void setBackButtonVisibility(int visibility) {
-        mBackButton.setVisibility(visibility);
-    }
-
-    /**
-     * Set the continue button text to given text.
-     */
-    public void setContinueButtonText(String text) {
-        mContinueButton.setText(text);
-    }
-
-    /**
-     * Set the continue button visibility to given visibility.
-     */
-    public void setContinueButtonVisibility(int visibility) {
-        mContinueButton.setVisibility(visibility);
+    private void setViewVisible(View button, boolean visible) {
+        button.setVisibility(visible ? View.VISIBLE : View.GONE);
     }
 
     /**
@@ -134,10 +149,67 @@
     }
 
     /**
-     * Set the continue button onClickListener to then given listener. Can be null if the listener
-     * should be overridden so no callback is made.
+     * Set the back button visibility to the given visibility.
      */
-    public void setContinueButtonListener(@Nullable View.OnClickListener listener) {
-        mContinueButton.setOnClickListener(listener);
+    public void setBackButtonVisible(boolean visible) {
+        setViewVisible(mBackButton, visible);
+    }
+
+    /**
+     * Set whether the primary continue button is enabled.
+     */
+    public void setPrimaryContinueButtonEnabled(boolean enabled) {
+        mPrimaryContinueButton.setEnabled(enabled);
+    }
+
+    /**
+     * Set the primary continue button onClickListener to the given listener. Can be null if the
+     * listener should be overridden so no callback is made.
+     */
+    public void setPrimaryContinueButtonListener(@Nullable View.OnClickListener listener) {
+        mPrimaryContinueButton.setOnClickListener(listener);
+    }
+
+    /**
+     * Set the primary continue button text to the given text.
+     */
+    public void setPrimaryContinueButtonText(String text) {
+        mPrimaryContinueButton.setText(text);
+    }
+
+    /**
+     * Set the primary continue button visibility to the given visibility.
+     */
+    public void setPrimaryContinueButtonVisible(boolean visible) {
+        setViewVisible(mPrimaryContinueButton, visible);
+    }
+
+    /**
+     * Set whether the secondary continue button is enabled.
+     */
+    public void setSecondaryContinueButtonEnabled(boolean enabled){
+        mSecondaryContinueButton.setEnabled(enabled);
+    }
+
+    /**
+     * Set the secondary continue button onClickListener to the given listener. Can be null if the
+     * listener should be overridden so no callback is made.
+     */
+    public void setSecondaryContinueButtonListener(@Nullable View.OnClickListener listener) {
+        mSecondaryContinueButton.setOnClickListener(listener);
+    }
+
+    /**
+     * Set the secondary continue button text to the given text.
+     */
+    public void setSecondaryContinueButtonText(String text) {
+        mSecondaryContinueButton.setText(text);
+    }
+
+    /**
+     * Set the secondary continue button visibility to the given visibility.
+     */
+    public void setSecondaryContinueButtonVisible(boolean visible) {
+        setViewVisible(mSecondaryContinueButton, visible);
     }
 }
diff --git a/com/android/commands/am/Am.java b/com/android/commands/am/Am.java
index ab075ee..813335a 100644
--- a/com/android/commands/am/Am.java
+++ b/com/android/commands/am/Am.java
@@ -98,7 +98,8 @@
     static final class MyShellCallback extends ShellCallback {
         boolean mActive = true;
 
-        @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+        @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
+                String mode) {
             if (!mActive) {
                 System.err.println("Open attempt after active for: " + path);
                 return null;
@@ -159,7 +160,11 @@
             } else if (opt.equals("-r")) {
                 instrument.rawMode = true;
             } else if (opt.equals("-m")) {
-                instrument.proto = true;
+                instrument.protoStd = true;
+            } else if (opt.equals("-f")) {
+                instrument.protoFile = true;
+                if (peekNextArg() != null && !peekNextArg().startsWith("-"))
+                    instrument.logPath = nextArg();
             } else if (opt.equals("-e")) {
                 final String argKey = nextArgRequired();
                 final String argValue = nextArgRequired();
diff --git a/com/android/commands/am/Instrument.java b/com/android/commands/am/Instrument.java
index b69ef1c..d79b1a6 100644
--- a/com/android/commands/am/Instrument.java
+++ b/com/android/commands/am/Instrument.java
@@ -25,23 +25,42 @@
 import android.content.pm.InstrumentationInfo;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.AndroidException;
 import android.util.proto.ProtoOutputStream;
 import android.view.IWindowManager;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
+import java.util.Locale;
 
 
 /**
  * Runs the am instrument command
+ *
+ * Test Result Code:
+ * 1 - Test running
+ * 0 - Test passed
+ * -2 - assertion failure
+ * -1 - other exceptions
+ *
+ * Session Result Code:
+ * -1: Success
+ * other: Failure
  */
 public class Instrument {
+    public static final String DEFAULT_LOG_DIR = "instrument-logs";
+
     private final IActivityManager mAm;
     private final IPackageManager mPm;
     private final IWindowManager mWm;
@@ -50,7 +69,9 @@
     public String profileFile = null;
     public boolean wait = false;
     public boolean rawMode = false;
-    public boolean proto = false;
+    boolean protoStd = false;  // write proto to stdout
+    boolean protoFile = false;  // write proto to a file
+    String logPath = null;
     public boolean noWindowAnimation = false;
     public String abi = null;
     public int userId = UserHandle.USER_CURRENT;
@@ -178,18 +199,49 @@
      * Printer for the protobuf based status reporting.
      */
     private class ProtoStatusReporter implements StatusReporter {
+
+        private File mLog;
+
+        ProtoStatusReporter() {
+            if (protoFile) {
+                if (logPath == null) {
+                    File logDir = new File(Environment.getLegacyExternalStorageDirectory(),
+                            DEFAULT_LOG_DIR);
+                    if (!logDir.exists() && !logDir.mkdirs()) {
+                        System.err.format("Unable to create log directory: %s\n",
+                                logDir.getAbsolutePath());
+                        protoFile = false;
+                        return;
+                    }
+                    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-hhmmss-SSS", Locale.US);
+                    String fileName = String.format("log-%s.instrumentation_data_proto",
+                            format.format(new Date()));
+                    mLog = new File(logDir, fileName);
+                } else {
+                    mLog = new File(Environment.getLegacyExternalStorageDirectory(), logPath);
+                    File logDir = mLog.getParentFile();
+                    if (!logDir.exists() && !logDir.mkdirs()) {
+                        System.err.format("Unable to create log directory: %s\n",
+                                logDir.getAbsolutePath());
+                        protoFile = false;
+                        return;
+                    }
+                }
+                if (mLog.exists()) mLog.delete();
+            }
+        }
+
         @Override
         public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
                 Bundle results) {
             final ProtoOutputStream proto = new ProtoOutputStream();
 
-            final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);
-
-            proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
+            final long token = proto.start(InstrumentationData.Session.TEST_STATUS);
+            proto.write(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
             writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
+            proto.end(token);
 
-            proto.endRepeatedObject(token);
-            writeProtoToStdout(proto);
+            outputProto(proto);
         }
 
         @Override
@@ -197,80 +249,87 @@
                 Bundle results) {
             final ProtoOutputStream proto = new ProtoOutputStream();
 
-            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
-
-            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+            final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
+            proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
                     InstrumentationData.SESSION_FINISHED);
-            proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
+            proto.write(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
             writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
+            proto.end(token);
 
-            proto.endObject(token);
-            writeProtoToStdout(proto);
+            outputProto(proto);
         }
 
         @Override
         public void onError(String errorText, boolean commandError) {
             final ProtoOutputStream proto = new ProtoOutputStream();
 
-            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);
-
-            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
+            final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
+            proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
                     InstrumentationData.SESSION_ABORTED);
-            proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+            proto.write(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
+            proto.end(token);
 
-            proto.endObject(token);
-            writeProtoToStdout(proto);
+            outputProto(proto);
         }
 
         private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
-            final long bundleToken = proto.startObject(fieldId);
+            final long bundleToken = proto.start(fieldId);
 
             for (final String key: sorted(bundle.keySet())) {
                 final long entryToken = proto.startRepeatedObject(
                         InstrumentationData.ResultsBundle.ENTRIES);
 
-                proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
+                proto.write(InstrumentationData.ResultsBundleEntry.KEY, key);
 
                 final Object val = bundle.get(key);
                 if (val instanceof String) {
-                    proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
                             (String)val);
                 } else if (val instanceof Byte) {
-                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT,
                             ((Byte)val).intValue());
                 } else if (val instanceof Double) {
-                    proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
-                            ((Double)val).doubleValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE, (double)val);
                 } else if (val instanceof Float) {
-                    proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
-                            ((Float)val).floatValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT, (float)val);
                 } else if (val instanceof Integer) {
-                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
-                            ((Integer)val).intValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (int)val);
                 } else if (val instanceof Long) {
-                    proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
-                            ((Long)val).longValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_LONG, (long)val);
                 } else if (val instanceof Short) {
-                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
-                            ((Short)val).intValue());
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (short)val);
                 } else if (val instanceof Bundle) {
                     writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
                             (Bundle)val);
+                } else if (val instanceof byte[]) {
+                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_BYTES, (byte[])val);
                 }
 
-                proto.endRepeatedObject(entryToken);
+                proto.end(entryToken);
             }
 
-            proto.endObject(bundleToken);
+            proto.end(bundleToken);
         }
 
-        private void writeProtoToStdout(ProtoOutputStream proto) {
-            try {
-                System.out.write(proto.getBytes());
-                System.out.flush();
-            } catch (IOException ex) {
-                System.err.println("Error writing finished response: ");
-                ex.printStackTrace(System.err);
+        private void outputProto(ProtoOutputStream proto) {
+            byte[] out = proto.getBytes();
+            if (protoStd) {
+                try {
+                    System.out.write(out);
+                    System.out.flush();
+                } catch (IOException ex) {
+                    System.err.println("Error writing finished response: ");
+                    ex.printStackTrace(System.err);
+                }
+            }
+            if (protoFile) {
+                try (OutputStream os = new FileOutputStream(mLog, true)) {
+                    os.write(proto.getBytes());
+                    os.flush();
+                } catch (IOException ex) {
+                    System.err.format("Cannot write to %s:\n", mLog.getAbsolutePath());
+                    ex.printStackTrace();
+                }
             }
         }
     }
@@ -374,7 +433,7 @@
 
         try {
             // Choose which output we will do.
-            if (proto) {
+            if (protoFile || protoStd) {
                 reporter = new ProtoStatusReporter();
             } else if (wait) {
                 reporter = new TextStatusReporter(rawMode);
@@ -396,7 +455,7 @@
                 mWm.setAnimationScale(2, 0.0f);
             }
 
-            // Figure out which component we are tring to do.
+            // Figure out which component we are trying to do.
             final ComponentName cn = parseComponentName(componentNameArg);
 
             // Choose an ABI if necessary
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
index 29433f3..9490880 100644
--- a/com/android/commands/pm/Pm.java
+++ b/com/android/commands/pm/Pm.java
@@ -157,7 +157,8 @@
     }
 
     static final class MyShellCallback extends ShellCallback {
-        @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+        @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
+                String mode) {
             File file = new File(path);
             final ParcelFileDescriptor fd;
             try {
diff --git a/com/android/commands/sm/Sm.java b/com/android/commands/sm/Sm.java
index a9a4118..77e8efa 100644
--- a/com/android/commands/sm/Sm.java
+++ b/com/android/commands/sm/Sm.java
@@ -20,6 +20,9 @@
 import static android.os.storage.StorageManager.PROP_HAS_ADOPTABLE;
 import static android.os.storage.StorageManager.PROP_VIRTUAL_DISK;
 
+import android.os.IBinder;
+import android.os.IVoldTaskListener;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -29,6 +32,8 @@
 import android.os.storage.VolumeInfo;
 import android.util.Log;
 
+import java.util.concurrent.CompletableFuture;
+
 public final class Sm {
     private static final String TAG = "Sm";
 
@@ -96,6 +101,8 @@
             runSetEmulateFbe();
         } else if ("get-fbe-mode".equals(op)) {
             runGetFbeMode();
+        } else if ("idle-maint".equals(op)) {
+            runIdleMaint();
         } else if ("fstrim".equals(op)) {
             runFstrim();
         } else if ("set-virtual-disk".equals(op)) {
@@ -221,9 +228,23 @@
         mSm.format(volId);
     }
 
-    public void runBenchmark() throws RemoteException {
+    public void runBenchmark() throws Exception {
         final String volId = nextArg();
-        mSm.benchmark(volId);
+        final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+        mSm.benchmark(volId, new IVoldTaskListener.Stub() {
+            @Override
+            public void onStatus(int status, PersistableBundle extras) {
+                // Ignored
+            }
+
+            @Override
+            public void onFinished(int status, PersistableBundle extras) {
+                // Touch to unparcel
+                extras.size();
+                result.complete(extras);
+            }
+        });
+        System.out.println(result.get());
     }
 
     public void runForget() throws RemoteException {
@@ -235,8 +256,22 @@
         }
     }
 
-    public void runFstrim() throws RemoteException {
-        mSm.fstrim(0);
+    public void runFstrim() throws Exception {
+        final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+        mSm.fstrim(0, new IVoldTaskListener.Stub() {
+            @Override
+            public void onStatus(int status, PersistableBundle extras) {
+                // Ignored
+            }
+
+            @Override
+            public void onFinished(int status, PersistableBundle extras) {
+                // Touch to unparcel
+                extras.size();
+                result.complete(extras);
+            }
+        });
+        System.out.println(result.get());
     }
 
     public void runSetVirtualDisk() throws RemoteException {
@@ -245,6 +280,15 @@
                 StorageManager.DEBUG_VIRTUAL_DISK);
     }
 
+    public void runIdleMaint() throws RemoteException {
+        final boolean im_run = "run".equals(nextArg());
+        if (im_run) {
+            mSm.runIdleMaintenance();
+        } else {
+            mSm.abortIdleMaintenance();
+        }
+    }
+
     private String nextArg() {
         if (mNextArg >= mArgs.length) {
             return null;
@@ -267,6 +311,7 @@
         System.err.println("       sm unmount VOLUME");
         System.err.println("       sm format VOLUME");
         System.err.println("       sm benchmark VOLUME");
+        System.err.println("       sm idle-maint [run|abort]");
         System.err.println("       sm fstrim");
         System.err.println("");
         System.err.println("       sm forget [UUID|all]");
diff --git a/com/android/ex/photo/ActionBarWrapper.java b/com/android/ex/photo/ActionBarWrapper.java
index 6d4d4d2..ae62197 100644
--- a/com/android/ex/photo/ActionBarWrapper.java
+++ b/com/android/ex/photo/ActionBarWrapper.java
@@ -1,7 +1,8 @@
 package com.android.ex.photo;
 
-import android.app.ActionBar;
+
 import android.graphics.drawable.Drawable;
+import android.support.v7.app.ActionBar;
 
 /**
  * Wrapper around {@link ActionBar}.
diff --git a/com/android/ex/photo/PhotoViewActivity.java b/com/android/ex/photo/PhotoViewActivity.java
index 7b53918..a5c4a43 100644
--- a/com/android/ex/photo/PhotoViewActivity.java
+++ b/com/android/ex/photo/PhotoViewActivity.java
@@ -21,14 +21,14 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
-import android.support.v4.app.FragmentActivity;
+import android.support.v7.app.AppCompatActivity;
 import android.view.Menu;
 import android.view.MenuItem;
 
 /**
  * Activity to view the contents of an album.
  */
-public class PhotoViewActivity extends FragmentActivity
+public class PhotoViewActivity extends AppCompatActivity
         implements PhotoViewController.ActivityInterface {
 
     private PhotoViewController mController;
@@ -41,7 +41,7 @@
         mController.onCreate(savedInstanceState);
     }
 
-    public PhotoViewController createController() {
+    protected PhotoViewController createController() {
         return new PhotoViewController(this);
     }
 
@@ -122,7 +122,7 @@
     @Override
     public ActionBarInterface getActionBarInterface() {
         if (mActionBar == null) {
-            mActionBar = new ActionBarWrapper(getActionBar());
+            mActionBar = new ActionBarWrapper(getSupportActionBar());
         }
         return mActionBar;
     }
diff --git a/com/android/ims/ImsManager.java b/com/android/ims/ImsManager.java
index a77abcd..813118b 100644
--- a/com/android/ims/ImsManager.java
+++ b/com/android/ims/ImsManager.java
@@ -37,8 +37,6 @@
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.telephony.ims.ImsServiceProxy;
-import android.telephony.ims.ImsServiceProxyCompat;
 import android.telephony.ims.feature.ImsFeature;
 import android.util.Log;
 
@@ -183,7 +181,7 @@
     private CarrierConfigManager mConfigManager;
     private int mPhoneId;
     private final boolean mConfigDynamicBind;
-    private ImsServiceProxyCompat mImsServiceProxy = null;
+    private ImsServiceProxy mImsServiceProxy = null;
     private ImsServiceDeathRecipient mDeathRecipient = new ImsServiceDeathRecipient();
     // Ut interface for the supplementary service configuration
     private ImsUt mUt = null;
@@ -237,6 +235,8 @@
     private static final long BACKOFF_MAX_DELAY_MS = 300000;
     // Multiplier for exponential delay
     private static final int BACKOFF_MULTIPLIER = 2;
+    // -1 indicates a subscriptionProperty value that is never set.
+    private static final int SUB_PROPERTY_NOT_INITIALIZED = -1;
 
 
     /**
@@ -281,20 +281,22 @@
     }
 
     /**
-     * Returns the user configuration of Enhanced 4G LTE Mode setting for slot.
+     * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If not set, it
+     * returns true as default value.
      */
     public boolean isEnhanced4gLteModeSettingEnabledByUser() {
         // If user can't edit Enhanced 4G LTE Mode, it assumes Enhanced 4G LTE Mode is always true.
         // If user changes SIM from editable mode to uneditable mode, need to return true.
-        if (!getBooleanCarrierConfig(
-                CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
+        if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
             return true;
         }
-        int enabled = android.provider.Settings.Global.getInt(
-                mContext.getContentResolver(),
-                android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED,
-                ImsConfig.FeatureValueConstants.ON);
-        return (enabled == 1);
+
+        int setting = SubscriptionManager.getIntegerSubscriptionProperty(
+                getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
+                SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+        // If it's never set, by default we return true.
+        return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
     }
 
     /**
@@ -319,28 +321,23 @@
      *
      */
     public void setEnhanced4gLteModeSetting(boolean enabled) {
-        // If false, we must always keep advanced 4G mode set to true (1).
-        int value = getBooleanCarrierConfig(
-                CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL) ? (enabled ? 1: 0) : 1;
+        // If false, we must always keep advanced 4G mode set to true.
+        enabled = getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
+                ? enabled : true;
 
-        try {
-            int prevSetting = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
-                    android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED);
-            if (prevSetting == value) {
-                // Don't trigger setAdvanced4GMode if the setting hasn't changed.
-                return;
-            }
-        } catch (Settings.SettingNotFoundException e) {
-            // Setting doesn't exist yet, so set it below.
-        }
+        int prevSetting = SubscriptionManager.getIntegerSubscriptionProperty(
+                getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
+                SUB_PROPERTY_NOT_INITIALIZED, mContext);
 
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED, value);
-        if (isNonTtyOrTtyOnVolteEnabled()) {
-            try {
-                setAdvanced4GMode(enabled);
-            } catch (ImsException ie) {
-                // do nothing
+        if (prevSetting != (enabled ? 1 : 0)) {
+            SubscriptionManager.setSubscriptionProperty(getSubId(),
+                    SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(enabled));
+            if (isNonTtyOrTtyOnVolteEnabled()) {
+                try {
+                    setAdvanced4GMode(enabled);
+                } catch (ImsException ie) {
+                    // do nothing
+                }
             }
         }
     }
@@ -406,8 +403,7 @@
 
         return mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_device_volte_available)
-                && getBooleanCarrierConfig(
-                        CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL)
+                && getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL)
                 && isGbaValid();
     }
 
@@ -541,8 +537,7 @@
 
         return mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_device_vt_available) &&
-                getBooleanCarrierConfig(
-                        CarrierConfigManager.KEY_CARRIER_VT_AVAILABLE_BOOL) &&
+                getBooleanCarrierConfig(CarrierConfigManager.KEY_CARRIER_VT_AVAILABLE_BOOL) &&
                 isGbaValid();
     }
 
@@ -562,13 +557,16 @@
     }
 
     /**
-     * Returns the user configuration of VT setting per slot.
+     * Returns the user configuration of VT setting per slot. If not set, it
+     * returns true as default value.
      */
     public boolean isVtEnabledByUser() {
-        int enabled = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.VT_IMS_ENABLED,
-                ImsConfig.FeatureValueConstants.ON);
-        return (enabled == 1);
+        int setting = SubscriptionManager.getIntegerSubscriptionProperty(
+                getSubId(), SubscriptionManager.VT_IMS_ENABLED,
+                SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+        // If it's never set, by default we return true.
+        return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
     }
 
     /**
@@ -589,10 +587,9 @@
      * Change persistent VT enabled setting for slot.
      */
     public void setVtSetting(boolean enabled) {
-        int value = enabled ? 1 : 0;
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.VT_IMS_ENABLED, value);
-
+        SubscriptionManager.setSubscriptionProperty(getSubId(),
+                SubscriptionManager.VT_IMS_ENABLED,
+                booleanToPropertyString(enabled));
         try {
             ImsConfig config = getConfigInterface();
             config.setFeatureValue(ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE,
@@ -662,15 +659,21 @@
     }
 
     /**
-     * Returns the user configuration of WFC setting for slot.
+     * Returns the user configuration of WFC setting for slot. If not set, it
+     * queries CarrierConfig value as default.
      */
     public boolean isWfcEnabledByUser() {
-        int enabled = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_ENABLED,
-                getBooleanCarrierConfig(
-                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL) ?
-                        ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
-        return enabled == 1;
+        int setting = SubscriptionManager.getIntegerSubscriptionProperty(
+                getSubId(), SubscriptionManager.WFC_IMS_ENABLED,
+                SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+        // SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
+        if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
+            return getBooleanCarrierConfig(
+                    CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL);
+        } else {
+            return setting == 1;
+        }
     }
 
     /**
@@ -691,9 +694,8 @@
      * Change persistent WFC enabled setting for slot.
      */
     public void setWfcSetting(boolean enabled) {
-        int value = enabled ? 1 : 0;
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_ENABLED, value);
+        SubscriptionManager.setSubscriptionProperty(getSubId(),
+                SubscriptionManager.WFC_IMS_ENABLED, booleanToPropertyString(enabled));
 
         setWfcNonPersistent(enabled, getWfcMode());
     }
@@ -736,7 +738,7 @@
     /**
      * Returns the user configuration of WFC preference setting.
      *
-     * @deprecated Doesn't support MSIM devices. Use {@link #getWfcMode()} instead.
+     * @deprecated Doesn't support MSIM devices. Use {@link #getWfcMode(boolean roaming)} instead.
      */
     public static int getWfcMode(Context context) {
         ImsManager mgr = ImsManager.getInstance(context,
@@ -750,13 +752,10 @@
 
     /**
      * Returns the user configuration of WFC preference setting
+     * @deprecated. Use {@link #getWfcMode(boolean roaming)} instead.
      */
     public int getWfcMode() {
-        int setting = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_MODE, getIntCarrierConfig(
-                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
-        if (DBG) log("getWfcMode - setting=" + setting);
-        return setting;
+        return getWfcMode(false);
     }
 
     /**
@@ -778,8 +777,9 @@
      */
     public void setWfcMode(int wfcMode) {
         if (DBG) log("setWfcMode(i) - setting=" + wfcMode);
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_MODE, wfcMode);
+
+        SubscriptionManager.setSubscriptionProperty(getSubId(),
+                SubscriptionManager.WFC_IMS_MODE, Integer.toString(wfcMode));
 
         setWfcModeInternal(wfcMode);
     }
@@ -813,22 +813,35 @@
     }
 
     /**
-     * Returns the user configuration of WFC preference setting for slot
+     * Returns the user configuration of WFC preference setting for slot. If not set, it
+     * queries CarrierConfig value as default.
      *
      * @param roaming {@code false} for home network setting, {@code true} for roaming  setting
      */
     public int getWfcMode(boolean roaming) {
         int setting = 0;
         if (!roaming) {
-            setting = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
-                    android.provider.Settings.Global.WFC_IMS_MODE, getIntCarrierConfig(
-                            CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
+            setting = SubscriptionManager.getIntegerSubscriptionProperty(
+                    getSubId(), SubscriptionManager.WFC_IMS_MODE,
+                    SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+            // SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
+            if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
+                setting = getIntCarrierConfig(
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT);
+            }
             if (DBG) log("getWfcMode - setting=" + setting);
         } else {
-            setting = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
-                    android.provider.Settings.Global.WFC_IMS_ROAMING_MODE,
-                    getIntCarrierConfig(
-                            CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT));
+            setting = SubscriptionManager.getIntegerSubscriptionProperty(
+                    getSubId(), SubscriptionManager.WFC_IMS_ROAMING_MODE,
+                    SUB_PROPERTY_NOT_INITIALIZED, mContext);
+
+            // SUB_PROPERTY_NOT_INITIALIZED indicates it's never set in sub db.
+            if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
+                setting = getIntCarrierConfig(
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT);
+            }
+
             if (DBG) log("getWfcMode (roaming) - setting=" + setting);
         }
         return setting;
@@ -859,15 +872,14 @@
     public void setWfcMode(int wfcMode, boolean roaming) {
         if (!roaming) {
             if (DBG) log("setWfcMode(i,b) - setting=" + wfcMode);
-            android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                    android.provider.Settings.Global.WFC_IMS_MODE, wfcMode);
+            SubscriptionManager.setSubscriptionProperty(getSubId(),
+                    SubscriptionManager.WFC_IMS_MODE, Integer.toString(wfcMode));
         } else {
             if (DBG) log("setWfcMode(i,b) (roaming) - setting=" + wfcMode);
-            android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                    android.provider.Settings.Global.WFC_IMS_ROAMING_MODE, wfcMode);
+            SubscriptionManager.setSubscriptionProperty(getSubId(),
+                    SubscriptionManager.WFC_IMS_ROAMING_MODE, Integer.toString(wfcMode));
         }
 
-
         TelephonyManager tm = (TelephonyManager)
                 mContext.getSystemService(Context.TELEPHONY_SERVICE);
         if (roaming == tm.isNetworkRoaming(getSubId())) {
@@ -907,13 +919,12 @@
     private void setWfcModeInternal(int wfcMode) {
         final int value = wfcMode;
         Thread thread = new Thread(() -> {
-                try {
-                    getConfigInterface().setProvisionedValue(
-                            ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE,
-                            value);
-                } catch (ImsException e) {
-                    // do nothing
-                }
+            try {
+                getConfigInterface().setProvisionedValue(
+                        ImsConfig.ConfigConstants.VOICE_OVER_WIFI_MODE, value);
+            } catch (ImsException e) {
+                // do nothing
+            }
         });
         thread.start();
     }
@@ -935,15 +946,19 @@
     }
 
     /**
-     * Returns the user configuration of WFC roaming setting for slot
+     * Returns the user configuration of WFC roaming setting for slot. If not set, it
+     * queries CarrierConfig value as default.
      */
     public boolean isWfcRoamingEnabledByUser() {
-        int enabled = android.provider.Settings.Global.getInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
-                getBooleanCarrierConfig(
-                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL) ?
-                        ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
-        return (enabled == 1);
+        int setting =  SubscriptionManager.getIntegerSubscriptionProperty(
+                getSubId(), SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
+                SUB_PROPERTY_NOT_INITIALIZED, mContext);
+        if (setting == SUB_PROPERTY_NOT_INITIALIZED) {
+            return getBooleanCarrierConfig(
+                            CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL);
+        } else {
+            return (setting == 1);
+        }
     }
 
     /**
@@ -962,10 +977,9 @@
      * Change persistent WFC roaming enabled setting
      */
     public void setWfcRoamingSetting(boolean enabled) {
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
-                enabled ? ImsConfig.FeatureValueConstants.ON
-                        : ImsConfig.FeatureValueConstants.OFF);
+        SubscriptionManager.setSubscriptionProperty(getSubId(),
+                SubscriptionManager.WFC_IMS_ROAMING_ENABLED, booleanToPropertyString(enabled)
+        );
 
         setWfcRoamingSettingInternal(enabled);
     }
@@ -975,13 +989,12 @@
                 ? ImsConfig.FeatureValueConstants.ON
                 : ImsConfig.FeatureValueConstants.OFF;
         Thread thread = new Thread(() -> {
-                try {
-                    getConfigInterface().setProvisionedValue(
-                            ImsConfig.ConfigConstants.VOICE_OVER_WIFI_ROAMING,
-                            value);
-                } catch (ImsException e) {
-                    // do nothing
-                }
+            try {
+                getConfigInterface().setProvisionedValue(
+                        ImsConfig.ConfigConstants.VOICE_OVER_WIFI_ROAMING, value);
+            } catch (ImsException e) {
+                // do nothing
+            }
         });
         thread.start();
     }
@@ -1361,6 +1374,14 @@
         return mImsServiceProxy.isBinderAlive();
     }
 
+    /*
+     * Returns a flag indicating whether the IMS service is ready to send requests to lower layers.
+     */
+    public boolean isServiceReady() {
+        connectIfServiceIsAvailable();
+        return mImsServiceProxy.isBinderReady();
+    }
+
     /**
      * If the service is available, try to reconnect.
      */
@@ -2383,34 +2404,30 @@
      */
     public void factoryReset() {
         // Set VoLTE to default
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.ENHANCED_4G_MODE_ENABLED,
-                ImsConfig.FeatureValueConstants.ON);
+        SubscriptionManager.setSubscriptionProperty(getSubId(),
+                SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(true));
 
         // Set VoWiFi to default
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_ENABLED,
-                getBooleanCarrierConfig(
-                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL) ?
-                        ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
+        SubscriptionManager.setSubscriptionProperty(getSubId(),
+                SubscriptionManager.WFC_IMS_ENABLED,
+                booleanToPropertyString(getBooleanCarrierConfig(
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL)));
 
         // Set VoWiFi mode to default
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_MODE,
-                getIntCarrierConfig(
-                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT));
+        SubscriptionManager.setSubscriptionProperty(getSubId(),
+                SubscriptionManager.WFC_IMS_MODE,
+                Integer.toString(getIntCarrierConfig(
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT)));
 
         // Set VoWiFi roaming to default
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.WFC_IMS_ROAMING_ENABLED,
-                getBooleanCarrierConfig(
-                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL) ?
-                        ImsConfig.FeatureValueConstants.ON : ImsConfig.FeatureValueConstants.OFF);
+        SubscriptionManager.setSubscriptionProperty(getSubId(),
+                SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
+                booleanToPropertyString(getBooleanCarrierConfig(
+                        CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL)));
 
         // Set VT to default
-        android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                android.provider.Settings.Global.VT_IMS_ENABLED,
-                ImsConfig.FeatureValueConstants.ON);
+        SubscriptionManager.setSubscriptionProperty(getSubId(),
+                SubscriptionManager.VT_IMS_ENABLED, booleanToPropertyString(true));
 
         // Push settings to ImsConfig
         updateImsServiceConfig(true);
@@ -2453,6 +2470,11 @@
         SystemProperties.set(VT_PROVISIONED_PROP, provisioned ? TRUE : FALSE);
     }
 
+    private static String booleanToPropertyString(boolean bool) {
+        return bool ? "1" : "0";
+    }
+
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("ImsManager:");
         pw.println("  mPhoneId = " + mPhoneId);
diff --git a/android/telephony/ims/ImsServiceProxy.java b/com/android/ims/ImsServiceProxy.java
similarity index 94%
rename from android/telephony/ims/ImsServiceProxy.java
rename to com/android/ims/ImsServiceProxy.java
index 038e295..8c51202 100644
--- a/android/telephony/ims/ImsServiceProxy.java
+++ b/com/android/ims/ImsServiceProxy.java
@@ -14,17 +14,15 @@
  * limitations under the License
  */
 
-package android.telephony.ims;
+package com.android.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;
@@ -41,9 +39,11 @@
  * @hide
  */
 
-public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeature {
+public class ImsServiceProxy {
 
     protected String LOG_TAG = "ImsServiceProxy";
+    protected final int mSlotId;
+    protected IBinder mBinder;
     private final int mSupportedFeature;
 
     // Start by assuming the proxy is available for usage.
@@ -99,13 +99,13 @@
     };
 
     public ImsServiceProxy(int slotId, IBinder binder, int featureType) {
-        super(slotId, binder);
+        mSlotId = slotId;
+        mBinder = binder;
         mSupportedFeature = featureType;
     }
 
     public ImsServiceProxy(int slotId, int featureType) {
-        super(slotId, null /*IBinder*/);
-        mSupportedFeature = featureType;
+        this(slotId, null, featureType);
     }
 
     public IImsServiceFeatureListener getListener() {
@@ -116,7 +116,6 @@
         mBinder = binder;
     }
 
-    @Override
     public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener)
             throws RemoteException {
         synchronized (mLock) {
@@ -126,7 +125,6 @@
         }
     }
 
-    @Override
     public void endSession(int sessionId) throws RemoteException {
         synchronized (mLock) {
             // Only check to make sure the binder connection still exists. This method should
@@ -136,7 +134,6 @@
         }
     }
 
-    @Override
     public boolean isConnected(int callServiceType, int callType)
             throws RemoteException {
         synchronized (mLock) {
@@ -146,7 +143,6 @@
         }
     }
 
-    @Override
     public boolean isOpened() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
@@ -154,7 +150,6 @@
         }
     }
 
-    @Override
     public void addRegistrationListener(IImsRegistrationListener listener)
     throws RemoteException {
         synchronized (mLock) {
@@ -164,7 +159,6 @@
         }
     }
 
-    @Override
     public void removeRegistrationListener(IImsRegistrationListener listener)
             throws RemoteException {
         synchronized (mLock) {
@@ -174,7 +168,6 @@
         }
     }
 
-    @Override
     public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType)
             throws RemoteException {
         synchronized (mLock) {
@@ -184,7 +177,6 @@
         }
     }
 
-    @Override
     public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
             IImsCallSessionListener listener) throws RemoteException {
         synchronized (mLock) {
@@ -194,7 +186,6 @@
         }
     }
 
-    @Override
     public IImsCallSession getPendingCallSession(int sessionId, String callId)
             throws RemoteException {
         synchronized (mLock) {
@@ -204,7 +195,6 @@
         }
     }
 
-    @Override
     public IImsUt getUtInterface() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
@@ -212,7 +202,6 @@
         }
     }
 
-    @Override
     public IImsConfig getConfigInterface() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
@@ -220,7 +209,6 @@
         }
     }
 
-    @Override
     public void turnOnIms() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
@@ -228,7 +216,6 @@
         }
     }
 
-    @Override
     public void turnOffIms() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
@@ -236,7 +223,6 @@
         }
     }
 
-    @Override
     public IImsEcbm getEcbmInterface() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
@@ -244,7 +230,6 @@
         }
     }
 
-    @Override
     public void setUiTTYMode(int uiTtyMode, Message onComplete)
             throws RemoteException {
         synchronized (mLock) {
@@ -254,7 +239,6 @@
         }
     }
 
-    @Override
     public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
@@ -263,7 +247,10 @@
         }
     }
 
-    @Override
+    /**
+     * @return an integer describing the current Feature Status, defined in
+     * {@link ImsFeature.ImsState}.
+     */
     public int getFeatureStatus() {
         synchronized (mLock) {
             if (isBinderAlive() && mFeatureStatusCached != null) {
@@ -318,7 +305,9 @@
         return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
     }
 
-    @Override
+    /**
+     * @return false if the binder connection is no longer alive.
+     */
     public boolean isBinderAlive() {
         return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
     }
@@ -332,4 +321,10 @@
     private IImsServiceController getServiceInterface(IBinder b) {
         return IImsServiceController.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/ImsServiceProxyCompat.java b/com/android/ims/ImsServiceProxyCompat.java
similarity index 86%
rename from android/telephony/ims/ImsServiceProxyCompat.java
rename to com/android/ims/ImsServiceProxyCompat.java
index bbd5f02..5ba1f35 100644
--- a/android/telephony/ims/ImsServiceProxyCompat.java
+++ b/com/android/ims/ImsServiceProxyCompat.java
@@ -14,16 +14,14 @@
  * limitations under the License
  */
 
-package android.telephony.ims;
+package com.android.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;
@@ -34,22 +32,18 @@
 import com.android.ims.internal.IImsUt;
 
 /**
- * Compatibility class that implements the new ImsService IMMTelFeature interface, but
+ * Compatibility class that implements the new ImsService MMTelFeature 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 {
+public class ImsServiceProxyCompat extends ImsServiceProxy {
 
     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;
+        super(slotId, binder, SERVICE_ID);
     }
 
     @Override
@@ -156,17 +150,12 @@
         checkBinderConnection();
         return getServiceInterface(mBinder).getMultiEndpointInterface(SERVICE_ID);
     }
-
-    /**
-     * Base implementation, always returns READY for compatibility with old ImsService.
-     */
+    @Override
     public int getFeatureStatus() {
         return ImsFeature.STATE_READY;
     }
 
-    /**
-     * @return false if the binder connection is no longer alive.
-     */
+    @Override
     public boolean isBinderAlive() {
         return mBinder != null && mBinder.isBinderAlive();
     }
@@ -174,10 +163,4 @@
     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/com/android/internal/app/NightDisplayController.java b/com/android/internal/app/ColorDisplayController.java
similarity index 94%
rename from com/android/internal/app/NightDisplayController.java
rename to com/android/internal/app/ColorDisplayController.java
index b2053c0..b8682a8 100644
--- a/com/android/internal/app/NightDisplayController.java
+++ b/com/android/internal/app/ColorDisplayController.java
@@ -25,6 +25,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.provider.Settings.Secure;
 import android.provider.Settings.System;
 import android.util.Slog;
@@ -41,14 +42,14 @@
 import java.time.format.DateTimeParseException;
 
 /**
- * Controller for managing Night display settings.
+ * Controller for managing night display and color mode settings.
  * <p/>
  * Night display tints your screen red at night. This makes it easier to look at your screen in
  * dim light and may help you fall asleep more easily.
  */
-public final class NightDisplayController {
+public final class ColorDisplayController {
 
-    private static final String TAG = "NightDisplayController";
+    private static final String TAG = "ColorDisplayController";
     private static final boolean DEBUG = false;
 
     @Retention(RetentionPolicy.SOURCE)
@@ -100,6 +101,12 @@
      */
     public static final int COLOR_MODE_SATURATED = 2;
 
+    /**
+     * See com.android.server.display.DisplayTransformManager.
+     */
+    private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
+    private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode";
+
     private final Context mContext;
     private final int mUserId;
 
@@ -107,11 +114,11 @@
 
     private Callback mCallback;
 
-    public NightDisplayController(@NonNull Context context) {
+    public ColorDisplayController(@NonNull Context context) {
         this(context, ActivityManager.getCurrentUser());
     }
 
-    public NightDisplayController(@NonNull Context context, int userId) {
+    public ColorDisplayController(@NonNull Context context, int userId) {
         mContext = context.getApplicationContext();
         mUserId = userId;
 
@@ -334,9 +341,15 @@
      */
     public int getColorMode() {
         final int colorMode = System.getIntForUser(mContext.getContentResolver(),
-            System.DISPLAY_COLOR_MODE, COLOR_MODE_BOOSTED, mUserId);
+            System.DISPLAY_COLOR_MODE, -1, mUserId);
         if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
-            return COLOR_MODE_BOOSTED;
+            // There still might be a legacy system property controlling color mode that we need to
+            // respect.
+            if ("1".equals(SystemProperties.get(PERSISTENT_PROPERTY_NATIVE_MODE))) {
+                return COLOR_MODE_SATURATED;
+            }
+            return "1.0".equals(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+                    ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
         }
         return colorMode;
     }
diff --git a/com/android/internal/app/LocaleHelper.java b/com/android/internal/app/LocaleHelper.java
index 386aa84..0a230a9 100644
--- a/com/android/internal/app/LocaleHelper.java
+++ b/com/android/internal/app/LocaleHelper.java
@@ -136,7 +136,16 @@
      * @return the localized country name.
      */
     public static String getDisplayCountry(Locale locale, Locale displayLocale) {
-        return ULocale.getDisplayCountry(locale.toLanguageTag(), ULocale.forLocale(displayLocale));
+        final String languageTag = locale.toLanguageTag();
+        final ULocale uDisplayLocale = ULocale.forLocale(displayLocale);
+        final String country = ULocale.getDisplayCountry(languageTag, uDisplayLocale);
+        final String numberingSystem = locale.getUnicodeLocaleType("nu");
+        if (numberingSystem != null) {
+            return String.format("%s (%s)", country,
+                    ULocale.getDisplayKeywordValue(languageTag, "numbers", uDisplayLocale));
+        } else {
+            return country;
+        }
     }
 
     /**
diff --git a/com/android/internal/app/LocalePicker.java b/com/android/internal/app/LocalePicker.java
index 9936ed5..c8c2fcf 100644
--- a/com/android/internal/app/LocalePicker.java
+++ b/com/android/internal/app/LocalePicker.java
@@ -93,10 +93,6 @@
         return context.getResources().getStringArray(R.array.supported_locales);
     }
 
-    public static String[] getPseudoLocales() {
-        return pseudoLocales;
-    }
-
     public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) {
         final Resources resources = context.getResources();
 
@@ -104,13 +100,6 @@
         List<String> localeList = new ArrayList<String>(locales.length);
         Collections.addAll(localeList, locales);
 
-        // Don't show the pseudolocales unless we're in developer mode. http://b/17190407.
-        if (!isInDeveloperMode) {
-            for (String locale : pseudoLocales) {
-                localeList.remove(locale);
-            }
-        }
-
         Collections.sort(localeList);
         final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
         final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
@@ -122,6 +111,10 @@
                     || l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
                 continue;
             }
+            // Don't show the pseudolocales unless we're in developer mode. http://b/17190407.
+            if (!isInDeveloperMode && LocaleList.isPseudoLocale(l)) {
+                continue;
+            }
 
             if (localeInfos.isEmpty()) {
                 if (DEBUG) {
diff --git a/com/android/internal/app/LocaleStore.java b/com/android/internal/app/LocaleStore.java
index e3fce51..2b0b5ee 100644
--- a/com/android/internal/app/LocaleStore.java
+++ b/com/android/internal/app/LocaleStore.java
@@ -17,6 +17,7 @@
 package com.android.internal.app;
 
 import android.content.Context;
+import android.os.LocaleList;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 
@@ -68,7 +69,9 @@
                 return null;
             }
             return new Locale.Builder()
-                    .setLocale(locale).setRegion("")
+                    .setLocale(locale)
+                    .setRegion("")
+                    .setExtension(Locale.UNICODE_LOCALE_EXTENSION, "")
                     .build();
         }
 
@@ -253,11 +256,25 @@
 
         Set<String> simCountries = getSimCountries(context);
 
+        final boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
         for (String localeId : LocalePicker.getSupportedLocales(context)) {
             if (localeId.isEmpty()) {
                 throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
             }
             LocaleInfo li = new LocaleInfo(localeId);
+
+            if (LocaleList.isPseudoLocale(li.getLocale())) {
+                if (isInDeveloperMode) {
+                    li.setTranslated(true);
+                    li.mIsPseudo = true;
+                    li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
+                } else {
+                    // Do not display pseudolocales unless in development mode.
+                    continue;
+                }
+            }
+
             if (simCountries.contains(li.getLocale().getCountry())) {
                 li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
             }
@@ -271,19 +288,6 @@
             }
         }
 
-        boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
-        for (String localeId : LocalePicker.getPseudoLocales()) {
-            LocaleInfo li = getLocaleInfo(Locale.forLanguageTag(localeId));
-            if (isInDeveloperMode) {
-                li.setTranslated(true);
-                li.mIsPseudo = true;
-                li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
-            } else {
-                sLocaleCache.remove(li.getId());
-            }
-        }
-
         // TODO: See if we can reuse what LocaleList.matchScore does
         final HashSet<String> localizedLocales = new HashSet<>();
         for (String localeId : LocalePicker.getSystemAssetLocales()) {
diff --git a/com/android/internal/colorextraction/types/Tonal.java b/com/android/internal/colorextraction/types/Tonal.java
index e6ef10b..71baaf1 100644
--- a/com/android/internal/colorextraction/types/Tonal.java
+++ b/com/android/internal/colorextraction/types/Tonal.java
@@ -51,9 +51,11 @@
 
     private static final boolean DEBUG = true;
 
+    public static final int THRESHOLD_COLOR_LIGHT = 0xffe0e0e0;
     public static final int MAIN_COLOR_LIGHT = 0xffe0e0e0;
     public static final int SECONDARY_COLOR_LIGHT = 0xff9e9e9e;
-    public static final int MAIN_COLOR_DARK = 0xff212121;
+    public static final int THRESHOLD_COLOR_DARK = 0xff212121;
+    public static final int MAIN_COLOR_DARK = 0xff000000;
     public static final int SECONDARY_COLOR_DARK = 0xff000000;
 
     private final TonalPalette mGreyPalette;
@@ -197,12 +199,12 @@
         // light fallback or darker than our dark fallback.
         ColorUtils.colorToHSL(mainColor, mTmpHSL);
         final float mainLuminosity = mTmpHSL[2];
-        ColorUtils.colorToHSL(MAIN_COLOR_LIGHT, mTmpHSL);
+        ColorUtils.colorToHSL(THRESHOLD_COLOR_LIGHT, mTmpHSL);
         final float lightLuminosity = mTmpHSL[2];
         if (mainLuminosity > lightLuminosity) {
             return false;
         }
-        ColorUtils.colorToHSL(MAIN_COLOR_DARK, mTmpHSL);
+        ColorUtils.colorToHSL(THRESHOLD_COLOR_DARK, mTmpHSL);
         final float darkLuminosity = mTmpHSL[2];
         if (mainLuminosity < darkLuminosity) {
             return false;
diff --git a/com/android/internal/net/NetworkStatsFactory.java b/com/android/internal/net/NetworkStatsFactory.java
index 3d3e148..5eda81b 100644
--- a/com/android/internal/net/NetworkStatsFactory.java
+++ b/com/android/internal/net/NetworkStatsFactory.java
@@ -43,6 +43,8 @@
 /**
  * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
  * files as needed.
+ *
+ * @hide
  */
 public class NetworkStatsFactory {
     private static final String TAG = "NetworkStatsFactory";
diff --git a/com/android/internal/os/BaseCommand.java b/com/android/internal/os/BaseCommand.java
index 3baccee..05ec9e9 100644
--- a/com/android/internal/os/BaseCommand.java
+++ b/com/android/internal/os/BaseCommand.java
@@ -106,6 +106,14 @@
     }
 
     /**
+     * Peek the next argument on the command line, whatever it is; if there are
+     * no arguments left, return null.
+     */
+    public String peekNextArg() {
+        return mArgs.peekNextArg();
+    }
+
+    /**
      * Return the next argument on the command line, whatever it is; if there are
      * no arguments left, throws an IllegalArgumentException to report this to the user.
      */
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index f0d05da..f2483c0 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -3639,6 +3639,7 @@
 
     public void addIsolatedUidLocked(int isolatedUid, int appUid) {
         mIsolatedUids.put(isolatedUid, appUid);
+        StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1);
     }
 
     /**
@@ -3659,9 +3660,11 @@
      * @see #scheduleRemoveIsolatedUidLocked(int, int)
      */
     public void removeIsolatedUidLocked(int isolatedUid) {
-        mIsolatedUids.delete(isolatedUid);
-        mKernelUidCpuTimeReader.removeUid(isolatedUid);
-        mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+      StatsLog.write(
+          StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
+      mIsolatedUids.delete(isolatedUid);
+      mKernelUidCpuTimeReader.removeUid(isolatedUid);
+      mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
     }
 
     public int mapUid(int uid) {
@@ -5412,6 +5415,18 @@
         }
     }
 
+    public String[] getWifiIfaces() {
+        synchronized (mWifiNetworkLock) {
+            return mWifiIfaces;
+        }
+    }
+
+    public String[] getMobileIfaces() {
+        synchronized (mModemNetworkLock) {
+            return mModemIfaces;
+        }
+    }
+
     @Override public long getScreenOnTime(long elapsedRealtimeUs, int which) {
         return mScreenOnTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
@@ -9614,7 +9629,8 @@
     }
 
     public boolean isScreenOn(int state) {
-        return state == Display.STATE_ON || state == Display.STATE_VR;
+        return state == Display.STATE_ON || state == Display.STATE_VR
+            || state == Display.STATE_ON_SUSPEND;
     }
 
     public boolean isScreenOff(int state) {
diff --git a/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index 8884d24..a39997d 100644
--- a/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.StrictMode;
 import android.os.SystemClock;
 import android.util.IntArray;
 import android.util.Slog;
@@ -82,6 +83,7 @@
         if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
             return null;
         }
+        final int oldMask = StrictMode.allowThreadDiskReadsMask();
         try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
             mProcFileAvailable = true;
             return readFreqs(reader, powerProfile);
@@ -89,6 +91,8 @@
             mReadErrorCounter++;
             Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
             return null;
+        } finally {
+            StrictMode.setThreadPolicyMask(oldMask);
         }
     }
 
@@ -106,12 +110,15 @@
         if (!mProcFileAvailable) {
             return;
         }
+        final int oldMask = StrictMode.allowThreadDiskReadsMask();
         try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
             mNowTimeMs = SystemClock.elapsedRealtime();
             readDelta(reader, callback);
             mLastTimeReadMs = mNowTimeMs;
         } catch (IOException e) {
             Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
+        } finally {
+            StrictMode.setThreadPolicyMask(oldMask);
         }
     }
 
diff --git a/com/android/internal/os/KernelUidCpuTimeReader.java b/com/android/internal/os/KernelUidCpuTimeReader.java
index 37d9d1d..65615c0 100644
--- a/com/android/internal/os/KernelUidCpuTimeReader.java
+++ b/com/android/internal/os/KernelUidCpuTimeReader.java
@@ -16,6 +16,7 @@
 package com.android.internal.os;
 
 import android.annotation.Nullable;
+import android.os.StrictMode;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -65,6 +66,7 @@
      *                 a fresh delta.
      */
     public void readDelta(@Nullable Callback callback) {
+        final int oldMask = StrictMode.allowThreadDiskReadsMask();
         long nowUs = SystemClock.elapsedRealtime() * 1000;
         try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
             TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
@@ -121,6 +123,8 @@
             }
         } catch (IOException e) {
             Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
+        } finally {
+            StrictMode.setThreadPolicyMask(oldMask);
         }
         mLastTimeReadUs = nowUs;
     }
@@ -160,12 +164,15 @@
 
     private void removeUidsFromKernelModule(int startUid, int endUid) {
         Slog.d(TAG, "Removing uids " + startUid + "-" + endUid);
+        final int oldMask = StrictMode.allowThreadDiskWritesMask();
         try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
             writer.write(startUid + "-" + endUid);
             writer.flush();
         } catch (IOException e) {
             Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid
                     + " from uid_cputime module", e);
+        } finally {
+            StrictMode.setThreadPolicyMask(oldMask);
         }
     }
 }
diff --git a/com/android/internal/policy/DividerSnapAlgorithm.java b/com/android/internal/policy/DividerSnapAlgorithm.java
index fb6b8b0..3af3e2a 100644
--- a/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -16,6 +16,10 @@
 
 package com.android.internal.policy;
 
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -99,11 +103,12 @@
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
             boolean isHorizontalDivision, Rect insets) {
-        this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, false);
+        this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets,
+                DOCKED_INVALID, false);
     }
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-            boolean isHorizontalDivision, Rect insets, boolean isMinimizedMode) {
+            boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode) {
         mMinFlingVelocityPxPerSecond =
                 MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
         mMinDismissVelocityPxPerSecond =
@@ -121,7 +126,7 @@
                 com.android.internal.R.dimen.default_minimal_size_resizable_task);
         mTaskHeightInMinimizedMode = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.task_height_of_minimized_mode);
-        calculateTargets(isHorizontalDivision);
+        calculateTargets(isHorizontalDivision, dockSide);
         mFirstSplitTarget = mTargets.get(1);
         mLastSplitTarget = mTargets.get(mTargets.size() - 2);
         mDismissStartTarget = mTargets.get(0);
@@ -254,7 +259,7 @@
         return mTargets.get(minIndex);
     }
 
-    private void calculateTargets(boolean isHorizontalDivision) {
+    private void calculateTargets(boolean isHorizontalDivision, int dockedSide) {
         mTargets.clear();
         int dividerMax = isHorizontalDivision
                 ? mDisplayHeight
@@ -273,7 +278,7 @@
                 addMiddleTarget(isHorizontalDivision);
                 break;
             case SNAP_MODE_MINIMIZED:
-                addMinimizedTarget(isHorizontalDivision);
+                addMinimizedTarget(isHorizontalDivision, dockedSide);
                 break;
         }
         mTargets.add(new SnapTarget(dividerMax - navBarSize, dividerMax,
@@ -331,12 +336,16 @@
         mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
     }
 
-    private void addMinimizedTarget(boolean isHorizontalDivision) {
+    private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
         // In portrait offset the position by the statusbar height, in landscape add the statusbar
         // height as well to match portrait offset
         int position = mTaskHeightInMinimizedMode + mInsets.top;
         if (!isHorizontalDivision) {
-            position += mInsets.left;
+            if (dockedSide == DOCKED_LEFT) {
+                position += mInsets.left;
+            } else if (dockedSide == DOCKED_RIGHT) {
+                position = mDisplayWidth - position - mInsets.right;
+            }
         }
         mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
     }
diff --git a/com/android/internal/telephony/BaseCommands.java b/com/android/internal/telephony/BaseCommands.java
index 137b2a7..9304f91 100644
--- a/com/android/internal/telephony/BaseCommands.java
+++ b/com/android/internal/telephony/BaseCommands.java
@@ -47,7 +47,6 @@
     protected RegistrantList mIccStatusChangedRegistrants = new RegistrantList();
     protected RegistrantList mVoicePrivacyOnRegistrants = new RegistrantList();
     protected RegistrantList mVoicePrivacyOffRegistrants = new RegistrantList();
-    protected Registrant mUnsolOemHookRawRegistrant;
     protected RegistrantList mOtaProvisionRegistrants = new RegistrantList();
     protected RegistrantList mCallWaitingInfoRegistrants = new RegistrantList();
     protected RegistrantList mDisplayInfoRegistrants = new RegistrantList();
@@ -584,17 +583,6 @@
         mSignalInfoRegistrants.add(r);
     }
 
-    public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
-        mUnsolOemHookRawRegistrant = new Registrant (h, what, obj);
-    }
-
-    public void unSetOnUnsolOemHookRaw(Handler h) {
-        if (mUnsolOemHookRawRegistrant != null && mUnsolOemHookRawRegistrant.getHandler() == h) {
-            mUnsolOemHookRawRegistrant.clear();
-            mUnsolOemHookRawRegistrant = null;
-        }
-    }
-
     @Override
     public void unregisterForSignalInfo(Handler h) {
         mSignalInfoRegistrants.remove(h);
diff --git a/com/android/internal/telephony/CommandsInterface.java b/com/android/internal/telephony/CommandsInterface.java
index f339693..7026ff2 100644
--- a/com/android/internal/telephony/CommandsInterface.java
+++ b/com/android/internal/telephony/CommandsInterface.java
@@ -1450,8 +1450,6 @@
      */
     void reportStkServiceIsRunning(Message result);
 
-    void invokeOemRilRequestRaw(byte[] data, Message response);
-
     /**
      * Sends carrier specific information to the vendor ril that can be used to
      * encrypt the IMSI and IMPI.
@@ -1464,14 +1462,6 @@
     void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
                                          Message response);
 
-    void invokeOemRilRequestStrings(String[] strings, Message response);
-
-    /**
-     * Fires when RIL_UNSOL_OEM_HOOK_RAW is received from the RIL.
-     */
-    void setOnUnsolOemHookRaw(Handler h, int what, Object obj);
-    void unSetOnUnsolOemHookRaw(Handler h);
-
     /**
      * Send TERMINAL RESPONSE to the SIM, after processing a proactive command
      * sent by the SIM.
diff --git a/com/android/internal/telephony/DefaultPhoneNotifier.java b/com/android/internal/telephony/DefaultPhoneNotifier.java
index 98c0a32..6a4dee7 100644
--- a/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -309,15 +309,6 @@
         }
     }
 
-    @Override
-    public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) {
-        try {
-            mRegistry.notifyOemHookRawEventForSubscriber(subId, rawData);
-        } catch (RemoteException ex) {
-            // system process is dead
-        }
-    }
-
     /**
      * Convert the {@link Phone.DataActivityState} enum into the TelephonyManager.DATA_* constants
      * for the public API.
diff --git a/com/android/internal/telephony/GsmCdmaPhone.java b/com/android/internal/telephony/GsmCdmaPhone.java
index ad078d6..47289e5 100644
--- a/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/com/android/internal/telephony/GsmCdmaPhone.java
@@ -1406,8 +1406,11 @@
 
         if (!isPhoneTypeGsm() && TextUtils.isEmpty(number)) {
             // Read platform settings for dynamic voicemail number
-            if (getContext().getResources().getBoolean(com.android.internal
-                    .R.bool.config_telephony_use_own_number_for_voicemail)) {
+            CarrierConfigManager configManager = (CarrierConfigManager)
+                    getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            PersistableBundle b = configManager.getConfig();
+            if (b != null && b.getBoolean(
+                    CarrierConfigManager.KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL)) {
                 number = getLine1Number();
             } else {
                 number = "*86";
@@ -2236,14 +2239,14 @@
                     int current_cdma_roaming_mode =
                             Settings.Global.getInt(getContext().getContentResolver(),
                             Settings.Global.CDMA_ROAMING_MODE,
-                            CarrierConfigManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
+                            TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
                     switch (config_cdma_roaming_mode) {
                         // Carrier's cdma_roaming_mode will overwrite the user's previous settings
                         // Keep the user's previous setting in global variable which will be used
                         // when carrier's setting is turn off.
-                        case CarrierConfigManager.CDMA_ROAMING_MODE_HOME:
-                        case CarrierConfigManager.CDMA_ROAMING_MODE_AFFILIATED:
-                        case CarrierConfigManager.CDMA_ROAMING_MODE_ANY:
+                        case TelephonyManager.CDMA_ROAMING_MODE_HOME:
+                        case TelephonyManager.CDMA_ROAMING_MODE_AFFILIATED:
+                        case TelephonyManager.CDMA_ROAMING_MODE_ANY:
                             logd("cdma_roaming_mode is going to changed to "
                                     + config_cdma_roaming_mode);
                             setCdmaRoamingPreference(config_cdma_roaming_mode,
@@ -2252,7 +2255,7 @@
 
                         // When carrier's setting is turn off, change the cdma_roaming_mode to the
                         // previous user's setting
-                        case CarrierConfigManager.CDMA_ROAMING_MODE_RADIO_DEFAULT:
+                        case TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT:
                             if (current_cdma_roaming_mode != config_cdma_roaming_mode) {
                                 logd("cdma_roaming_mode is going to changed to "
                                         + current_cdma_roaming_mode);
diff --git a/com/android/internal/telephony/IccSmsInterfaceManager.java b/com/android/internal/telephony/IccSmsInterfaceManager.java
index 0fc08c6..997ccea 100644
--- a/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -78,6 +78,8 @@
     protected static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
     private static final int SMS_CB_CODE_SCHEME_MIN = 0;
     private static final int SMS_CB_CODE_SCHEME_MAX = 255;
+    public static final int SMS_MESSAGE_PRIORITY_NOT_SPECIFIED = -1;
+    public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1;
 
     protected Phone mPhone;
     final protected Context mContext;
@@ -393,7 +395,8 @@
                 Manifest.permission.SEND_SMS,
                 "Sending SMS message");
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-            persistMessageForNonDefaultSmsApp);
+            persistMessageForNonDefaultSmsApp, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+            false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
     }
 
     /**
@@ -407,7 +410,8 @@
                 Manifest.permission.SEND_SMS,
                 "Sending SMS message");
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-            persistMessage);
+            persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
+            SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
     }
 
     /**
@@ -433,15 +437,39 @@
      * @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 persistMessageForNonDefaultSmsApp whether the sent message should
+     *  be automatically persisted in the SMS db. It only affects messages sent
+     *  by a non-default SMS app. Currently only the carrier app can set this
+     *  parameter to false to skip auto message persistence.
+     * @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 including 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 including negative considered as Invalid Validity Period of the message.
      */
 
     private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessageForNonDefaultSmsApp) {
+            boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
+            int validityPeriod) {
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
                 " text='"+ text + "' sentIntent=" +
-                sentIntent + " deliveryIntent=" + deliveryIntent);
+                sentIntent + " deliveryIntent=" + deliveryIntent
+                + " priority=" + priority + " expectMore=" + expectMore
+                + " validityPeriod=" + validityPeriod);
         }
         if (mAppOps.noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
                 callingPackage) != AppOpsManager.MODE_ALLOWED) {
@@ -452,7 +480,65 @@
         }
         destAddr = filterDestAddress(destAddr);
         mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
-                null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp);
+                null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp,
+                priority, expectMore, validityPeriod);
+    }
+
+    /**
+     * Send a text based SMS with Messaging Options.
+     *
+     * @param destAddr the address to send the message to
+     * @param scAddr 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 persistMessageForNonDefaultSmsApp whether the sent message should
+     *  be automatically persisted in the SMS db. It only affects messages sent
+     *  by a non-default SMS app. Currently only the carrier app can set this
+     *  parameter to false to skip auto message persistence.
+     * @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 including 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 including negative considered as Invalid Validity Period of the message.
+     */
+
+    public void sendTextWithOptions(String callingPackage, String destAddr, String scAddr,
+            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
+            int validityPeriod) {
+        mPhone.getContext().enforceCallingOrSelfPermission(
+                Manifest.permission.SEND_SMS,
+                "Sending SMS message");
+        sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
+                persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod);
     }
 
     /**
@@ -504,6 +590,63 @@
     public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
             List<String> parts, List<PendingIntent> sentIntents,
             List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp) {
+        sendMultipartTextWithOptions(callingPackage, destAddr, destAddr, parts, sentIntents,
+                deliveryIntents, persistMessageForNonDefaultSmsApp,
+                SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
+                SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
+    }
+
+    /**
+     * Send a multi-part text based SMS with Messaging Options.
+     *
+     * @param destAddr the address to send the message to
+     * @param scAddr 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:
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
+     *   <code>RESULT_ERROR_RADIO_OFF</code>
+     *   <code>RESULT_ERROR_NULL_PDU</code>.
+     *  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 persistMessageForNonDefaultSmsApp whether the sent message should
+     *   be automatically persisted in the SMS db. It only affects messages sent
+     *   by a non-default SMS app. Currently only the carrier app can set this
+     *   parameter to false to skip auto message persistence.
+     * @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 including 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 including negative considered as Invalid Validity Period of the message.
+     */
+
+    public void sendMultipartTextWithOptions(String callingPackage, String destAddr,
+            String scAddr, List<String> parts, List<PendingIntent> sentIntents,
+            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
+            int priority, boolean expectMore, int validityPeriod) {
         mPhone.getContext().enforceCallingPermission(
                 Manifest.permission.SEND_SMS,
                 "Sending SMS message");
@@ -514,7 +657,7 @@
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             int i = 0;
             for (String part : parts) {
-                log("sendMultipartText: destAddr=" + destAddr + ", srAddr=" + scAddr +
+                log("sendMultipartTextWithOptions: destAddr=" + destAddr + ", srAddr=" + scAddr +
                         ", part[" + (i++) + "]=" + part);
             }
         }
@@ -549,17 +692,21 @@
                 mDispatcher.sendText(destAddr, scAddr, singlePart,
                         singleSentIntent, singleDeliveryIntent,
                         null/*messageUri*/, callingPackage,
-                        persistMessageForNonDefaultSmsApp);
+                        persistMessageForNonDefaultSmsApp,
+                        priority, expectMore, validityPeriod);
             }
             return;
         }
 
-        mDispatcher.sendMultipartText(destAddr, scAddr, (ArrayList<String>) parts,
-                (ArrayList<PendingIntent>) sentIntents, (ArrayList<PendingIntent>) deliveryIntents,
-                null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp);
+        mDispatcher.sendMultipartText(destAddr,
+                                      scAddr,
+                                      (ArrayList<String>) parts,
+                                      (ArrayList<PendingIntent>) sentIntents,
+                                      (ArrayList<PendingIntent>) deliveryIntents,
+                                      null, callingPackage, persistMessageForNonDefaultSmsApp,
+                                      priority, expectMore, validityPeriod);
     }
 
-
     public int getPremiumSmsPermission(String packageName) {
         return mDispatcher.getPremiumSmsPermission(packageName);
     }
@@ -953,7 +1100,8 @@
         textAndAddress[1] = filterDestAddress(textAndAddress[1]);
         mDispatcher.sendText(textAndAddress[1], scAddress, textAndAddress[0],
                 sentIntent, deliveryIntent, messageUri, callingPkg,
-                true /* persistMessageForNonDefaultSmsApp */);
+                true /* persistMessageForNonDefaultSmsApp */, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+                false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
     }
 
     public void sendStoredMultipartText(String callingPkg, Uri messageUri, String scAddress,
@@ -1009,7 +1157,9 @@
 
                 mDispatcher.sendText(textAndAddress[1], scAddress, singlePart,
                         singleSentIntent, singleDeliveryIntent, messageUri, callingPkg,
-                        true  /* persistMessageForNonDefaultSmsApp */);
+                        true  /* persistMessageForNonDefaultSmsApp */,
+                        SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+                        false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
             }
             return;
         }
@@ -1022,7 +1172,10 @@
                 (ArrayList<PendingIntent>) deliveryIntents,
                 messageUri,
                 callingPkg,
-                true  /* persistMessageForNonDefaultSmsApp */);
+                true  /* persistMessageForNonDefaultSmsApp */,
+                SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+                false /* expectMore */,
+                SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
     }
 
     private boolean isFailedOrDraft(ContentResolver resolver, Uri messageUri) {
diff --git a/com/android/internal/telephony/ImsSMSDispatcher.java b/com/android/internal/telephony/ImsSMSDispatcher.java
index 4d8f62c..bc829d6 100644
--- a/com/android/internal/telephony/ImsSMSDispatcher.java
+++ b/com/android/internal/telephony/ImsSMSDispatcher.java
@@ -173,13 +173,15 @@
     public void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
-            boolean persistMessage) {
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
         if (isCdmaMo()) {
             mCdmaDispatcher.sendMultipartText(destAddr, scAddr,
-                    parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage);
+                    parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage,
+                    priority, expectMore, validityPeriod);
         } else {
             mGsmDispatcher.sendMultipartText(destAddr, scAddr,
-                    parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage);
+                    parts, sentIntents, deliveryIntents, messageUri, callingPkg, persistMessage,
+                    priority, expectMore, validityPeriod);
         }
     }
 
@@ -199,14 +201,16 @@
     @Override
     public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
             PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
-            boolean persistMessage) {
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
         Rlog.d(TAG, "sendText");
         if (isCdmaMo()) {
             mCdmaDispatcher.sendText(destAddr, scAddr,
-                    text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage);
+                    text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage,
+                    priority, expectMore, validityPeriod);
         } else {
             mGsmDispatcher.sendText(destAddr, scAddr,
-                    text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage);
+                    text, sentIntent, deliveryIntent, messageUri, callingPkg, persistMessage,
+                    priority, expectMore, validityPeriod);
         }
     }
 
@@ -365,7 +369,7 @@
             String message, SmsHeader smsHeader, int format, PendingIntent sentIntent,
             PendingIntent deliveryIntent, boolean lastPart,
             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
-            String fullMessageText) {
+            String fullMessageText, int priority, boolean expectMore, int validityPeriod) {
         Rlog.e(TAG, "Error! Not implemented for IMS.");
         return null;
     }
diff --git a/com/android/internal/telephony/InboundSmsHandler.java b/com/android/internal/telephony/InboundSmsHandler.java
index 391de50..2d663cd 100644
--- a/com/android/internal/telephony/InboundSmsHandler.java
+++ b/com/android/internal/telephony/InboundSmsHandler.java
@@ -165,9 +165,9 @@
      * state */
     private static final int EVENT_STATE_TIMEOUT = 10;
 
-    /** Timeout duration for EVENT_STATE_TIMEOUT */
+    /** Timeout duration for EVENT_STATE_TIMEOUT (5 minutes) */
     @VisibleForTesting
-    public static final int STATE_TIMEOUT = 30000;
+    public static final int STATE_TIMEOUT = 5 * 60 * 1000;
 
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKELOCK_TIMEOUT = 3000;
diff --git a/com/android/internal/telephony/MccTable.java b/com/android/internal/telephony/MccTable.java
index 5714b29..3220078 100644
--- a/com/android/internal/telephony/MccTable.java
+++ b/com/android/internal/telephony/MccTable.java
@@ -32,7 +32,7 @@
 import com.android.internal.app.LocaleStore.LocaleInfo;
 
 import libcore.icu.ICU;
-import libcore.icu.TimeZoneNames;
+import libcore.util.TimeZoneFinder;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -94,24 +94,8 @@
         if (entry == null) {
             return null;
         }
-        Locale locale = new Locale("", entry.mIso);
-        String[] tz = TimeZoneNames.forLocale(locale);
-        if (tz.length == 0) return null;
-
-        String zoneName = tz[0];
-
-        /* Use Australia/Sydney instead of Australia/Lord_Howe for Australia.
-         * http://b/33228250
-         * Todo: remove the code, see b/62418027
-         */
-        if (mcc == 505  /* Australia / Norfolk Island */) {
-            for (String zone : tz) {
-                if (zone.contains("Sydney")) {
-                    zoneName = zone;
-                }
-            }
-        }
-        return zoneName;
+        final String lowerCaseCountryCode = entry.mIso;
+        return TimeZoneFinder.getInstance().lookupDefaultTimeZoneIdByCountry(lowerCaseCountryCode);
     }
 
     /**
@@ -434,7 +418,7 @@
         String country = MccTable.countryCodeForMcc(mcc);
         Slog.d(LOG_TAG, "WIFI_COUNTRY_CODE set to " + country);
         WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        wM.setCountryCode(country, false);
+        wM.setCountryCode(country);
     }
 
     static {
diff --git a/com/android/internal/telephony/NitzData.java b/com/android/internal/telephony/NitzData.java
new file mode 100644
index 0000000..6775639
--- /dev/null
+++ b/com/android/internal/telephony/NitzData.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.telephony.Rlog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Represents NITZ data. Various static methods are provided to help with parsing and intepretation
+ * of NITZ data.
+ *
+ * {@hide}
+ */
+@VisibleForTesting(visibility = PACKAGE)
+public final class NitzData {
+    private static final String LOG_TAG = ServiceStateTracker.LOG_TAG;
+    private static final int MS_PER_HOUR = 60 * 60 * 1000;
+    private static final int MS_PER_QUARTER_HOUR = 15 * 60 * 1000;
+
+    /* Time stamp after 19 January 2038 is not supported under 32 bit */
+    private static final int MAX_NITZ_YEAR = 2037;
+
+    // Stored For logging / debugging only.
+    private final String mOriginalString;
+
+    private final int mZoneOffset;
+
+    private final Integer mDstOffset;
+
+    private final long mCurrentTimeMillis;
+
+    private final TimeZone mEmulatorHostTimeZone;
+
+    private NitzData(String originalString, int zoneOffsetMillis, Integer dstOffsetMillis,
+            long utcTimeMillis, TimeZone timeZone) {
+        this.mOriginalString = originalString;
+        this.mZoneOffset = zoneOffsetMillis;
+        this.mDstOffset = dstOffsetMillis;
+        this.mCurrentTimeMillis = utcTimeMillis;
+        this.mEmulatorHostTimeZone = timeZone;
+    }
+
+    /**
+     * Parses the supplied NITZ string, returning the encoded data.
+     */
+    public static NitzData parse(String nitz) {
+        // "yy/mm/dd,hh:mm:ss(+/-)tz[,dt[,tzid]]"
+        // tz, dt are in number of quarter-hours
+
+        try {
+            /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
+             * offset as well (which we won't worry about until later) */
+            Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+            c.clear();
+            c.set(Calendar.DST_OFFSET, 0);
+
+            String[] nitzSubs = nitz.split("[/:,+-]");
+
+            int year = 2000 + Integer.parseInt(nitzSubs[0]);
+            if (year > MAX_NITZ_YEAR) {
+                if (ServiceStateTracker.DBG) {
+                    Rlog.e(LOG_TAG, "NITZ year: " + year + " exceeds limit, skip NITZ time update");
+                }
+                return null;
+            }
+            c.set(Calendar.YEAR, year);
+
+            // month is 0 based!
+            int month = Integer.parseInt(nitzSubs[1]) - 1;
+            c.set(Calendar.MONTH, month);
+
+            int date = Integer.parseInt(nitzSubs[2]);
+            c.set(Calendar.DATE, date);
+
+            int hour = Integer.parseInt(nitzSubs[3]);
+            c.set(Calendar.HOUR, hour);
+
+            int minute = Integer.parseInt(nitzSubs[4]);
+            c.set(Calendar.MINUTE, minute);
+
+            int second = Integer.parseInt(nitzSubs[5]);
+            c.set(Calendar.SECOND, second);
+
+            // The offset received from NITZ is the offset to add to get current local time.
+            boolean sign = (nitz.indexOf('-') == -1);
+            int totalUtcOffsetQuarterHours = Integer.parseInt(nitzSubs[6]);
+            int totalUtcOffsetMillis =
+                    (sign ? 1 : -1) * totalUtcOffsetQuarterHours * MS_PER_QUARTER_HOUR;
+
+            // DST correction is already applied to the UTC offset. We could subtract it if we
+            // wanted the raw offset.
+            Integer dstAdjustmentQuarterHours =
+                    (nitzSubs.length >= 8) ? Integer.parseInt(nitzSubs[7]) : null;
+            Integer dstAdjustmentMillis = null;
+            if (dstAdjustmentQuarterHours != null) {
+                dstAdjustmentMillis = dstAdjustmentQuarterHours * MS_PER_QUARTER_HOUR;
+            }
+
+            // As a special extension, the Android emulator appends the name of
+            // the host computer's timezone to the nitz string. this is zoneinfo
+            // timezone name of the form Area!Location or Area!Location!SubLocation
+            // so we need to convert the ! into /
+            TimeZone zone = null;
+            if (nitzSubs.length >= 9) {
+                String tzname = nitzSubs[8].replace('!', '/');
+                zone = TimeZone.getTimeZone(tzname);
+            }
+            return new NitzData(nitz, totalUtcOffsetMillis, dstAdjustmentMillis,
+                    c.getTimeInMillis(), zone);
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "NITZ: Parsing NITZ time " + nitz + " ex=" + ex);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the current time as the number of milliseconds since the beginning of the Unix epoch
+     * (1/1/1970 00:00:00 UTC).
+     */
+    public long getCurrentTimeInMillis() {
+        return mCurrentTimeMillis;
+    }
+
+    /**
+     * Returns the total offset to apply to the {@link #getCurrentTimeInMillis()} to arrive at a
+     * local time.
+     */
+    public int getLocalOffsetMillis() {
+        return mZoneOffset;
+    }
+
+    /**
+     * Returns the offset (already included in {@link #getLocalOffsetMillis()}) associated with
+     * Daylight Savings Time (DST). This field is optional: {@code null} means the DST offset is
+     * unknown.
+     */
+    public Integer getDstAdjustmentMillis() {
+        return mDstOffset;
+    }
+
+    /**
+     * Returns {@link true} if the time is in Daylight Savings Time (DST), {@link false} if it is
+     * unknown or not in DST. See {@link #getDstAdjustmentMillis()}.
+     */
+    public boolean isDst() {
+        return mDstOffset != null && mDstOffset != 0;
+    }
+
+
+    /**
+     * Returns the time zone of the host computer when Android is running in an emulator. It is
+     * {@code null} for real devices. This information is communicated via a non-standard Android
+     * extension to NITZ.
+     */
+    public TimeZone getEmulatorHostTimeZone() {
+        return mEmulatorHostTimeZone;
+    }
+
+    /**
+     * Using information present in the supplied {@link NitzData} object, guess the time zone.
+     * Because multiple time zones can have the same offset / DST state at a given time this process
+     * is error prone; an arbitrary match is returned when there are multiple candidates. The
+     * algorithm can also return a non-exact match by assuming that the DST information provided by
+     * NITZ is incorrect. This method can return {@code null} if no time zones are found.
+     */
+    public static TimeZone guessTimeZone(NitzData nitzData) {
+        int offset = nitzData.getLocalOffsetMillis();
+        boolean dst = nitzData.isDst();
+        long when = nitzData.getCurrentTimeInMillis();
+        TimeZone guess = findTimeZone(offset, dst, when);
+        if (guess == null) {
+            // Couldn't find a proper timezone.  Perhaps the DST data is wrong.
+            guess = findTimeZone(offset, !dst, when);
+        }
+        return guess;
+    }
+
+    private static TimeZone findTimeZone(int offset, boolean dst, long when) {
+        int rawOffset = offset;
+        if (dst) {
+            rawOffset -= MS_PER_HOUR;
+        }
+        String[] zones = TimeZone.getAvailableIDs(rawOffset);
+        TimeZone guess = null;
+        Date d = new Date(when);
+        for (String zone : zones) {
+            TimeZone tz = TimeZone.getTimeZone(zone);
+            if (tz.getOffset(when) == offset && tz.inDaylightTime(d) == dst) {
+                guess = tz;
+                break;
+            }
+        }
+
+        return guess;
+    }
+
+    @Override
+    public String toString() {
+        return "NitzData{"
+                + "mOriginalString=" + mOriginalString
+                + ", mZoneOffset=" + mZoneOffset
+                + ", mDstOffset=" + mDstOffset
+                + ", mCurrentTimeMillis=" + mCurrentTimeMillis
+                + ", mEmulatorHostTimeZone=" + mEmulatorHostTimeZone
+                + '}';
+    }
+}
diff --git a/com/android/internal/telephony/OemHookIndication.java b/com/android/internal/telephony/OemHookIndication.java
deleted file mode 100644
index 122a70e..0000000
--- a/com/android/internal/telephony/OemHookIndication.java
+++ /dev/null
@@ -1,53 +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 com.android.internal.telephony;
-
-import android.hardware.radio.deprecated.V1_0.IOemHookIndication;
-import android.os.AsyncResult;
-
-import java.util.ArrayList;
-
-import static com.android.internal.telephony.RILConstants.RIL_UNSOL_OEM_HOOK_RAW;
-
-/**
- * Class containing oem hook indication callbacks
- */
-public class OemHookIndication extends IOemHookIndication.Stub {
-    RIL mRil;
-
-    public OemHookIndication(RIL ril) {
-        mRil = ril;
-    }
-
-    /**
-     * @param indicationType RadioIndicationType
-     * @param data Data sent by oem
-     */
-    public void oemHookRaw(int indicationType, ArrayList<Byte> data) {
-        mRil.processIndication(indicationType);
-
-        byte[] response = RIL.arrayListToPrimitiveArray(data);
-        if (RIL.RILJ_LOGD) {
-            mRil.unsljLogvRet(RIL_UNSOL_OEM_HOOK_RAW,
-                    com.android.internal.telephony.uicc.IccUtils.bytesToHexString(response));
-        }
-
-        if (mRil.mUnsolOemHookRawRegistrant != null) {
-            mRil.mUnsolOemHookRawRegistrant.notifyRegistrant(new AsyncResult(null, response, null));
-        }
-    }
-}
diff --git a/com/android/internal/telephony/OemHookResponse.java b/com/android/internal/telephony/OemHookResponse.java
deleted file mode 100644
index 0afeac8..0000000
--- a/com/android/internal/telephony/OemHookResponse.java
+++ /dev/null
@@ -1,59 +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 com.android.internal.telephony;
-
-import android.hardware.radio.deprecated.V1_0.IOemHookResponse;
-import android.hardware.radio.V1_0.RadioError;
-import android.hardware.radio.V1_0.RadioResponseInfo;
-
-import java.util.ArrayList;
-
-/**
- * Class containing oem hook response callbacks
- */
-public class OemHookResponse extends IOemHookResponse.Stub {
-    RIL mRil;
-
-    public OemHookResponse(RIL ril) {
-        mRil = ril;
-    }
-
-    /**
-     * @param responseInfo Response info struct containing response type, serial no. and error
-     * @param data Data returned by oem
-     */
-    public void sendRequestRawResponse(RadioResponseInfo responseInfo, ArrayList<Byte> data) {
-        RILRequest rr = mRil.processResponse(responseInfo);
-
-        if (rr != null) {
-            byte[] ret = null;
-            if (responseInfo.error == RadioError.NONE) {
-                ret = RIL.arrayListToPrimitiveArray(data);
-                RadioResponse.sendMessageResponse(rr.mResult, ret);
-            }
-            mRil.processResponseDone(rr, responseInfo, ret);
-        }
-    }
-
-    /**
-     * @param responseInfo Response info struct containing response type, serial no. and error
-     * @param data Data returned by oem
-     */
-    public void sendRequestStringsResponse(RadioResponseInfo responseInfo, ArrayList<String> data) {
-        RadioResponse.responseStringArrayList(mRil, responseInfo, data);
-    }
-}
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index cf52097..c51a5fe 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -188,7 +188,6 @@
     private static final int EVENT_SRVCC_STATE_CHANGED              = 31;
     private static final int EVENT_INITIATE_SILENT_REDIAL           = 32;
     private static final int EVENT_RADIO_NOT_AVAILABLE              = 33;
-    private static final int EVENT_UNSOL_OEM_HOOK_RAW               = 34;
     protected static final int EVENT_GET_RADIO_CAPABILITY           = 35;
     protected static final int EVENT_SS                             = 36;
     private static final int EVENT_CONFIG_LCE                       = 37;
@@ -527,7 +526,7 @@
                 // note this is not persisting
                 WifiManager wM = (WifiManager)
                         mContext.getSystemService(Context.WIFI_SERVICE);
-                wM.setCountryCode(country, false);
+                wM.setCountryCode(country);
             }
         }
 
@@ -541,7 +540,6 @@
         if (getPhoneType() != PhoneConstants.PHONE_TYPE_SIP) {
             mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
         }
-        mCi.setOnUnsolOemHookRaw(this, EVENT_UNSOL_OEM_HOOK_RAW, null);
         mCi.startLceService(DEFAULT_REPORT_INTERVAL_MS, LCE_PULL_MODE,
                 obtainMessage(EVENT_CONFIG_LCE));
     }
@@ -677,16 +675,6 @@
                 }
                 break;
 
-            case EVENT_UNSOL_OEM_HOOK_RAW:
-                ar = (AsyncResult)msg.obj;
-                if (ar.exception == null) {
-                    byte[] data = (byte[])ar.result;
-                    mNotifier.notifyOemHookRawEventForSubscriber(getSubId(), data);
-                } else {
-                    Rlog.e(LOG_TAG, "OEM hook raw exception: " + ar.exception);
-                }
-                break;
-
             case EVENT_CONFIG_LCE:
                 ar = (AsyncResult) msg.obj;
                 if (ar.exception != null) {
@@ -2050,45 +2038,6 @@
     }
 
     /**
-     * Invokes RIL_REQUEST_OEM_HOOK_RAW on RIL implementation.
-     *
-     * @param data The data for the request.
-     * @param response <strong>On success</strong>,
-     * (byte[])(((AsyncResult)response.obj).result)
-     * <strong>On failure</strong>,
-     * (((AsyncResult)response.obj).result) == null and
-     * (((AsyncResult)response.obj).exception) being an instance of
-     * com.android.internal.telephony.gsm.CommandException
-     *
-     * @see #invokeOemRilRequestRaw(byte[], android.os.Message)
-     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
-     */
-    @Deprecated
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-        mCi.invokeOemRilRequestRaw(data, response);
-    }
-
-    /**
-     * Invokes RIL_REQUEST_OEM_HOOK_Strings on RIL implementation.
-     *
-     * @param strings The strings to make available as the request data.
-     * @param response <strong>On success</strong>, "response" bytes is
-     * made available as:
-     * (String[])(((AsyncResult)response.obj).result).
-     * <strong>On failure</strong>,
-     * (((AsyncResult)response.obj).result) == null and
-     * (((AsyncResult)response.obj).exception) being an instance of
-     * com.android.internal.telephony.gsm.CommandException
-     *
-     * @see #invokeOemRilRequestStrings(java.lang.String[], android.os.Message)
-     * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
-     */
-    @Deprecated
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-        mCi.invokeOemRilRequestStrings(strings, response);
-    }
-
-    /**
      * Read one of the NV items defined in {@link RadioNVItems} / {@code ril_nv_items.h}.
      * Used for device configuration by some CDMA operators.
      *
diff --git a/com/android/internal/telephony/PhoneInternalInterface.java b/com/android/internal/telephony/PhoneInternalInterface.java
index 7dbd8d3..4c39a65 100644
--- a/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/com/android/internal/telephony/PhoneInternalInterface.java
@@ -21,13 +21,13 @@
 import android.os.Message;
 import android.os.ResultReceiver;
 import android.os.WorkSource;
-import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
 import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 
-import com.android.internal.telephony.PhoneConstants.*; // ????
+import com.android.internal.telephony.PhoneConstants.DataState;
 
 import java.util.List;
 
@@ -165,11 +165,11 @@
 
     // Used for CDMA roaming mode
     // Home Networks only, as defined in PRL
-    static final int CDMA_RM_HOME        = CarrierConfigManager.CDMA_ROAMING_MODE_HOME;
+    int CDMA_RM_HOME        = TelephonyManager.CDMA_ROAMING_MODE_HOME;
     // Roaming an Affiliated networks, as defined in PRL
-    static final int CDMA_RM_AFFILIATED  = CarrierConfigManager.CDMA_ROAMING_MODE_AFFILIATED;
+    int CDMA_RM_AFFILIATED  = TelephonyManager.CDMA_ROAMING_MODE_AFFILIATED;
     // Roaming on Any Network, as defined in PRL
-    static final int CDMA_RM_ANY         = CarrierConfigManager.CDMA_ROAMING_MODE_ANY;
+    int CDMA_RM_ANY         = TelephonyManager.CDMA_ROAMING_MODE_ANY;
 
     // Used for CDMA subscription mode
     static final int CDMA_SUBSCRIPTION_UNKNOWN  =-1; // Unknown
diff --git a/com/android/internal/telephony/PhoneNotifier.java b/com/android/internal/telephony/PhoneNotifier.java
index 2caf5e2..3b29a06 100644
--- a/com/android/internal/telephony/PhoneNotifier.java
+++ b/com/android/internal/telephony/PhoneNotifier.java
@@ -62,6 +62,4 @@
     public void notifyVoiceActivationStateChanged(Phone sender, int activationState);
 
     public void notifyDataActivationStateChanged(Phone sender, int activationState);
-
-    public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData);
 }
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 9007f14..3ddbf12 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -52,7 +52,6 @@
 import android.hardware.radio.V1_0.SimApdu;
 import android.hardware.radio.V1_0.SmsWriteArgs;
 import android.hardware.radio.V1_0.UusInfo;
-import android.hardware.radio.deprecated.V1_0.IOemHook;
 import android.net.ConnectivityManager;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -174,9 +173,6 @@
     RadioResponse mRadioResponse;
     RadioIndication mRadioIndication;
     volatile IRadio mRadioProxy = null;
-    OemHookResponse mOemHookResponse;
-    OemHookIndication mOemHookIndication;
-    volatile IOemHook mOemHookProxy = null;
     final AtomicLong mRadioProxyCookie = new AtomicLong(0);
     final RadioProxyDeathRecipient mRadioProxyDeathRecipient;
     final RilHandler mRilHandler;
@@ -280,7 +276,6 @@
                         // todo: rild should be back up since message was sent with a delay. this is
                         // a hack.
                         getRadioProxy(null);
-                        getOemHookProxy(null);
                     }
                     break;
             }
@@ -325,7 +320,6 @@
 
     private void resetProxyAndRequestList() {
         mRadioProxy = null;
-        mOemHookProxy = null;
 
         // increment the cookie so that death notification can be ignored
         mRadioProxyCookie.incrementAndGet();
@@ -389,55 +383,6 @@
         return mRadioProxy;
     }
 
-    /** Returns an {@link IOemHook} instance or null if the service is not available. */
-    @VisibleForTesting
-    public IOemHook getOemHookProxy(Message result) {
-        if (!mIsMobileNetworkSupported) {
-            if (RILJ_LOGV) riljLog("getOemHookProxy: Not calling getService(): wifi-only");
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
-            return null;
-        }
-
-        if (mOemHookProxy != null) {
-            return mOemHookProxy;
-        }
-
-        try {
-            mOemHookProxy = IOemHook.getService(
-                    HIDL_SERVICE_NAME[mPhoneId == null ? 0 : mPhoneId]);
-            if (mOemHookProxy != null) {
-                // not calling linkToDeath() as ril service runs in the same process and death
-                // notification for that should be sufficient
-                mOemHookProxy.setResponseFunctions(mOemHookResponse, mOemHookIndication);
-            } else {
-                riljLoge("getOemHookProxy: mOemHookProxy == null");
-            }
-        } catch (RemoteException | RuntimeException e) {
-            mOemHookProxy = null;
-            riljLoge("OemHookProxy getService/setResponseFunctions: " + e);
-        }
-
-        if (mOemHookProxy == null) {
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
-                result.sendToTarget();
-            }
-
-            // if service is not up, treat it like death notification to try to get service again
-            mRilHandler.sendMessageDelayed(
-                    mRilHandler.obtainMessage(EVENT_RADIO_PROXY_DEAD,
-                            mRadioProxyCookie.incrementAndGet()),
-                    IRADIO_GET_SERVICE_DELAY_MILLIS);
-        }
-
-        return mOemHookProxy;
-    }
-
     //***** Constructors
 
     public RIL(Context context, int preferredNetworkType, int cdmaSubscription) {
@@ -464,8 +409,6 @@
 
         mRadioResponse = new RadioResponse(this);
         mRadioIndication = new RadioIndication(this);
-        mOemHookResponse = new OemHookResponse(this);
-        mOemHookIndication = new OemHookIndication(this);
         mRilHandler = new RilHandler();
         mRadioProxyDeathRecipient = new RadioProxyDeathRecipient();
 
@@ -488,7 +431,6 @@
         // set radio callback; needed to set RadioIndication callback (should be done after
         // wakelock stuff is initialized above as callbacks are received on separate binder threads)
         getRadioProxy(null);
-        getOemHookProxy(null);
     }
 
     @Override
@@ -1953,51 +1895,6 @@
     }
 
     @Override
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-        IOemHook oemHookProxy = getOemHookProxy(response);
-        if (oemHookProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_OEM_HOOK_RAW, response,
-                    mRILDefaultWorkSource);
-
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                        + "[" + IccUtils.bytesToHexString(data) + "]");
-            }
-
-            try {
-                oemHookProxy.sendRequestRaw(rr.mSerial, primitiveArrayToArrayList(data));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "invokeOemRilRequestRaw", e);
-            }
-        }
-    }
-
-    @Override
-    public void invokeOemRilRequestStrings(String[] strings, Message result) {
-        IOemHook oemHookProxy = getOemHookProxy(result);
-        if (oemHookProxy != null) {
-            RILRequest rr = obtainRequest(RIL_REQUEST_OEM_HOOK_STRINGS, result,
-                    mRILDefaultWorkSource);
-
-            String logStr = "";
-            for (int i = 0; i < strings.length; i++) {
-                logStr = logStr + strings[i] + " ";
-            }
-            if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " strings = "
-                        + logStr);
-            }
-
-            try {
-                oemHookProxy.sendRequestStrings(rr.mSerial,
-                        new ArrayList<String>(Arrays.asList(strings)));
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(rr, "invokeOemRilRequestStrings", e);
-            }
-        }
-    }
-
-    @Override
     public void setSuppServiceNotifications(boolean enable, Message result) {
         IRadio radioProxy = getRadioProxy(result);
         if (radioProxy != null) {
@@ -4480,10 +4377,6 @@
                 return "DATA_CALL_LIST";
             case RIL_REQUEST_RESET_RADIO:
                 return "RESET_RADIO";
-            case RIL_REQUEST_OEM_HOOK_RAW:
-                return "OEM_HOOK_RAW";
-            case RIL_REQUEST_OEM_HOOK_STRINGS:
-                return "OEM_HOOK_STRINGS";
             case RIL_REQUEST_SCREEN_STATE:
                 return "SCREEN_STATE";
             case RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION:
@@ -4709,8 +4602,6 @@
                 return "UNSOL_CDMA_OTA_PROVISION_STATUS";
             case RIL_UNSOL_CDMA_INFO_REC:
                 return "UNSOL_CDMA_INFO_REC";
-            case RIL_UNSOL_OEM_HOOK_RAW:
-                return "UNSOL_OEM_HOOK_RAW";
             case RIL_UNSOL_RINGBACK_TONE:
                 return "UNSOL_RINGBACK_TONE";
             case RIL_UNSOL_RESEND_INCALL_MUTE:
diff --git a/com/android/internal/telephony/RadioResponse.java b/com/android/internal/telephony/RadioResponse.java
index caf4477..3990d24 100644
--- a/com/android/internal/telephony/RadioResponse.java
+++ b/com/android/internal/telephony/RadioResponse.java
@@ -576,9 +576,6 @@
         responseDataCallList(responseInfo, dataCallResultList);
     }
 
-    public void sendOemRilRequestRawResponse(RadioResponseInfo responseInfo,
-                                             ArrayList<Byte> var2) {}
-
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
diff --git a/com/android/internal/telephony/SMSDispatcher.java b/com/android/internal/telephony/SMSDispatcher.java
index 2ec5101..dc08ec0 100644
--- a/com/android/internal/telephony/SMSDispatcher.java
+++ b/com/android/internal/telephony/SMSDispatcher.java
@@ -17,6 +17,8 @@
 package com.android.internal.telephony;
 
 import static android.Manifest.permission.SEND_SMS_NO_CONFIRMATION;
+import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
+import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
 import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
 import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
 import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
@@ -797,10 +799,29 @@
      * @param callingPkg the calling package name
      * @param persistMessage whether to save the sent message into SMS DB for a
      *   non-default SMS app.
+     *
+     * @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.
      */
     protected abstract void sendText(String destAddr, String scAddr, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
-            String callingPkg, boolean persistMessage);
+            String callingPkg, boolean persistMessage, int priority, boolean expectMore,
+            int validityPeriod);
 
     /**
      * Inject an SMS PDU into the android platform.
@@ -852,11 +873,28 @@
      * @param callingPkg the calling package name
      * @param persistMessage whether to save the sent message into SMS DB for a
      *   non-default SMS app.
+     * @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.
      */
     protected void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
-            boolean persistMessage) {
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
         final String fullMessageText = getMultipartMessageText(parts);
         int refNumber = getNextConcatenatedRef() & 0x00FF;
         int msgCount = parts.size();
@@ -913,7 +951,8 @@
             trackers[i] =
                 getNewSubmitPduTracker(destAddr, scAddr, parts.get(i), smsHeader, encoding,
                         sentIntent, deliveryIntent, (i == (msgCount - 1)),
-                        unsentPartCount, anyPartFailed, messageUri, fullMessageText);
+                        unsentPartCount, anyPartFailed, messageUri,
+                        fullMessageText, priority, expectMore, validityPeriod);
             trackers[i].mPersistMessage = persistMessage;
         }
 
@@ -943,11 +982,12 @@
     /**
      * Create a new SubmitPdu and return the SMS tracker.
      */
-    protected abstract SmsTracker getNewSubmitPduTracker(String destinationAddress, String scAddress,
-            String message, SmsHeader smsHeader, int encoding,
+    protected abstract SmsTracker getNewSubmitPduTracker(String destinationAddress,
+            String scAddress, String message, SmsHeader smsHeader, int encoding,
             PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
-            AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
-            String fullMessageText);
+            AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed,
+            Uri messageUri, String fullMessageText,
+            int priority, boolean expectMore, int validityPeriod);
 
     /**
      * Send an SMS
@@ -1320,7 +1360,8 @@
         }
 
         sendMultipartText(destinationAddress, scAddress, parts, sentIntents, deliveryIntents,
-                null/*messageUri*/, null/*callingPkg*/, tracker.mPersistMessage);
+                null/*messageUri*/, null/*callingPkg*/, tracker.mPersistMessage, tracker.mPriority,
+                tracker.mExpectMore, tracker.mValidityPeriod);
     }
 
     /**
@@ -1334,6 +1375,8 @@
         public int mImsRetry; // nonzero indicates initial message was sent over Ims
         public int mMessageRef;
         public boolean mExpectMore;
+        public int mValidityPeriod;
+        public int mPriority;
         String mFormat;
 
         public final PendingIntent mSentIntent;
@@ -1367,8 +1410,9 @@
         private SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
                 PendingIntent deliveryIntent, PackageInfo appInfo, String destAddr, String format,
                 AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
-                SmsHeader smsHeader, boolean isExpectMore, String fullMessageText, int subId,
-                boolean isText, boolean persistMessage, int userId) {
+                SmsHeader smsHeader, boolean expectMore, String fullMessageText, int subId,
+                boolean isText, boolean persistMessage, int userId, int priority,
+                int validityPeriod) {
             mData = data;
             mSentIntent = sentIntent;
             mDeliveryIntent = deliveryIntent;
@@ -1376,7 +1420,7 @@
             mAppInfo = appInfo;
             mDestAddress = destAddr;
             mFormat = format;
-            mExpectMore = isExpectMore;
+            mExpectMore = expectMore;
             mImsRetry = 0;
             mMessageRef = 0;
             mUnsentPartCount = unsentPartCount;
@@ -1388,6 +1432,8 @@
             mIsText = isText;
             mPersistMessage = persistMessage;
             mUserId = userId;
+            mPriority = priority;
+            mValidityPeriod = validityPeriod;
         }
 
         /**
@@ -1603,7 +1649,8 @@
     protected SmsTracker getSmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
             PendingIntent deliveryIntent, String format, AtomicInteger unsentPartCount,
             AtomicBoolean anyPartFailed, Uri messageUri, SmsHeader smsHeader,
-            boolean isExpectMore, String fullMessageText, boolean isText, boolean persistMessage) {
+            boolean expectMore, String fullMessageText, boolean isText, boolean persistMessage,
+            int priority, int validityPeriod) {
         // Get calling app package name via UID from Binder call
         PackageManager pm = mContext.getPackageManager();
         String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());
@@ -1624,16 +1671,27 @@
         // and before displaying the number to the user if confirmation is required.
         String destAddr = PhoneNumberUtils.extractNetworkPortion((String) data.get("destAddr"));
         return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format,
-                unsentPartCount, anyPartFailed, messageUri, smsHeader, isExpectMore,
-                fullMessageText, getSubId(), isText, persistMessage, userId);
+                unsentPartCount, anyPartFailed, messageUri, smsHeader, expectMore,
+                fullMessageText, getSubId(), isText, persistMessage, userId, priority,
+                validityPeriod);
     }
 
     protected SmsTracker getSmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, String format, Uri messageUri, boolean isExpectMore,
+            PendingIntent deliveryIntent, String format, Uri messageUri, boolean expectMore,
             String fullMessageText, boolean isText, boolean persistMessage) {
         return getSmsTracker(data, sentIntent, deliveryIntent, format, null/*unsentPartCount*/,
-                null/*anyPartFailed*/, messageUri, null/*smsHeader*/, isExpectMore,
-                fullMessageText, isText, persistMessage);
+                null/*anyPartFailed*/, messageUri, null/*smsHeader*/, expectMore,
+                fullMessageText, isText, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+                SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
+    }
+
+    protected SmsTracker getSmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
+            PendingIntent deliveryIntent, String format, Uri messageUri, boolean expectMore,
+            String fullMessageText, boolean isText, boolean persistMessage, int priority,
+            int validityPeriod) {
+        return getSmsTracker(data, sentIntent, deliveryIntent, format, null/*unsentPartCount*/,
+                null/*anyPartFailed*/, messageUri, null/*smsHeader*/, expectMore, fullMessageText,
+                isText, persistMessage, priority, validityPeriod);
     }
 
     protected HashMap<String, Object> getSmsTrackerMap(String destAddr, String scAddr,
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index e0f94de..0f4c9e4 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -94,8 +94,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Date;
 import java.util.List;
 import java.util.TimeZone;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -105,8 +103,8 @@
  * {@hide}
  */
 public class ServiceStateTracker extends Handler {
-    private static final String LOG_TAG = "SST";
-    private static final boolean DBG = true;
+    static final String LOG_TAG = "SST";
+    static final boolean DBG = true;
     private static final boolean VDBG = false;  // STOPSHIP if true
 
     private static final String PROP_FORCE_ROAMING = "telephony.test.forceRoaming";
@@ -381,9 +379,7 @@
      * we can fix the time zone once know the country.
      */
     private boolean mNeedFixZoneAfterNitz = false;
-    private int mZoneOffset;
-    private boolean mZoneDst;
-    private long mZoneTime;
+    private NitzData mNitzData;
     private boolean mGotCountryCode = false;
     private String mSavedTimeZone;
     private long mSavedTime;
@@ -2254,7 +2250,7 @@
             IccRecords iccRecords = mIccRecords;
             String plmn = null;
             boolean showPlmn = false;
-            int rule = (iccRecords != null) ? iccRecords.getDisplayRule(mSS.getOperatorNumeric()) : 0;
+            int rule = (iccRecords != null) ? iccRecords.getDisplayRule(mSS) : 0;
             if (combinedRegState == ServiceState.STATE_OUT_OF_SERVICE
                     || combinedRegState == ServiceState.STATE_EMERGENCY_ONLY) {
                 showPlmn = true;
@@ -2530,11 +2526,11 @@
         mRatLog.log(mSS.toString());
     }
 
-    protected void log(String s) {
+    protected final void log(String s) {
         Rlog.d(LOG_TAG, s);
     }
 
-    protected void loge(String s) {
+    protected final void loge(String s) {
         Rlog.e(LOG_TAG, s);
     }
 
@@ -3132,12 +3128,12 @@
         }
     }
 
-    protected boolean isInvalidOperatorNumeric(String operatorNumeric) {
+    private boolean isInvalidOperatorNumeric(String operatorNumeric) {
         return operatorNumeric == null || operatorNumeric.length() < 5 ||
                 operatorNumeric.startsWith(INVALID_MCC);
     }
 
-    protected String fixUnknownMcc(String operatorNumeric, int sid) {
+    private String fixUnknownMcc(String operatorNumeric, int sid) {
         if (sid <= 0) {
             // no cdma information is available, do nothing
             return operatorNumeric;
@@ -3147,45 +3143,53 @@
         // if mSavedTimeZone is null, TimeZone would get the default timeZone,
         // and the fixTimeZone couldn't help, because it depends on operator Numeric;
         // if the sid is conflict and timezone is unavailable, the mcc may be not right.
-        boolean isNitzTimeZone = false;
-        int timeZone = 0;
-        TimeZone tzone = null;
+        boolean isNitzTimeZone;
+        int rawUtcOffsetMillis;
         if (mSavedTimeZone != null) {
-            timeZone =
-                    TimeZone.getTimeZone(mSavedTimeZone).getRawOffset()/MS_PER_HOUR;
+            rawUtcOffsetMillis = TimeZone.getTimeZone(mSavedTimeZone).getRawOffset();
             isNitzTimeZone = true;
         } else {
-            tzone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
-            if (tzone != null)
-                timeZone = tzone.getRawOffset()/MS_PER_HOUR;
+            rawUtcOffsetMillis = 0;
+            if (mNitzData != null) {
+                TimeZone tzone = NitzData.guessTimeZone(mNitzData);
+                if (tzone != null) {
+                    rawUtcOffsetMillis = tzone.getRawOffset();
+                }
+            }
+            isNitzTimeZone = false;
         }
+        int rawUtcOffsetHours = rawUtcOffsetMillis / MS_PER_HOUR;
 
-        int mcc = mHbpcdUtils.getMcc(sid,
-                timeZone, (mZoneDst ? 1 : 0), isNitzTimeZone);
+        boolean isDst = mNitzData != null && mNitzData.isDst();
+        int mcc = mHbpcdUtils.getMcc(sid, rawUtcOffsetHours, (isDst ? 1 : 0), isNitzTimeZone);
         if (mcc > 0) {
             operatorNumeric = Integer.toString(mcc) + DEFAULT_MNC;
         }
         return operatorNumeric;
     }
 
-    protected void fixTimeZone(String isoCountryCode) {
+    private void fixTimeZone(String isoCountryCode) {
         TimeZone zone = null;
         // If the offset is (0, false) and the time zone property
         // is set, use the time zone property rather than GMT.
         final String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
         if (DBG) {
-            log("fixTimeZone zoneName='" + zoneName +
-                    "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
-                    " iso-cc='" + isoCountryCode +
-                    "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode));
+            log("fixTimeZone zoneName='" + zoneName
+                    + "' mNitzData=" + mNitzData
+                    + " iso-cc='" + isoCountryCode
+                    + "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode));
         }
         if ("".equals(isoCountryCode) && mNeedFixZoneAfterNitz) {
             // Country code not found.  This is likely a test network.
             // Get a TimeZone based only on the NITZ parameters (best guess).
-            zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
+
+            // mNeedFixZoneAfterNitz is only set to true when mNitzData is set so there's no need to
+            // check mNitzData == null.
+            zone = NitzData.guessTimeZone(mNitzData);
             if (DBG) log("pollStateDone: using NITZ TimeZone");
-        } else if ((mZoneOffset == 0) && (mZoneDst == false) && (zoneName != null)
-                && (zoneName.length() > 0)
+        } else if ((mNitzData == null
+                        || (mNitzData.getLocalOffsetMillis() == 0 && !mNitzData.isDst()))
+                && (zoneName != null) && (zoneName.length() > 0)
                 && (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) {
             // For NITZ string without time zone,
             // need adjust time to reflect default time zone setting
@@ -3209,12 +3213,20 @@
             }
             if (DBG) log("fixTimeZone: using default TimeZone");
         } else {
-            zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, isoCountryCode);
+            if (mNitzData == null) {
+                // This behavior is unusual but consistent with historical behavior when it wasn't
+                // possible to detect whether a previous NITZ signal had been saved.
+                zone = TimeUtils.getTimeZone(0 /* offset */, false /* dst */, 0 /* when */,
+                        isoCountryCode);
+            } else {
+                zone = TimeUtils.getTimeZone(mNitzData.getLocalOffsetMillis(), mNitzData.isDst(),
+                        mNitzData.getCurrentTimeInMillis(), isoCountryCode);
+            }
             if (DBG) log("fixTimeZone: using getTimeZone(off, dst, time, iso)");
         }
 
-        final String tmpLog = "fixTimeZone zoneName=" + zoneName + " mZoneOffset=" + mZoneOffset
-                + " mZoneDst=" + mZoneDst + " iso-cc=" + isoCountryCode + " mNeedFixZoneAfterNitz="
+        final String tmpLog = "fixTimeZone zoneName=" + zoneName + " mNitzData=" + mNitzData
+                + " iso-cc=" + isoCountryCode + " mNeedFixZoneAfterNitz="
                 + mNeedFixZoneAfterNitz + " zone=" + (zone != null ? zone.getID() : "NULL");
         mTimeZoneLog.log(tmpLog);
 
@@ -3246,39 +3258,6 @@
                 (dataRegState != ServiceState.STATE_IN_SERVICE));
     }
 
-    /**
-     * Returns a TimeZone object based only on parameters from the NITZ string.
-     */
-    private TimeZone getNitzTimeZone(int offset, boolean dst, long when) {
-        TimeZone guess = findTimeZone(offset, dst, when);
-        if (guess == null) {
-            // Couldn't find a proper timezone.  Perhaps the DST data is wrong.
-            guess = findTimeZone(offset, !dst, when);
-        }
-        if (DBG) log("getNitzTimeZone returning " + (guess == null ? guess : guess.getID()));
-        return guess;
-    }
-
-    private TimeZone findTimeZone(int offset, boolean dst, long when) {
-        int rawOffset = offset;
-        if (dst) {
-            rawOffset -= MS_PER_HOUR;
-        }
-        String[] zones = TimeZone.getAvailableIDs(rawOffset);
-        TimeZone guess = null;
-        Date d = new Date(when);
-        for (String zone : zones) {
-            TimeZone tz = TimeZone.getTimeZone(zone);
-            if (tz.getOffset(when) == offset &&
-                    tz.inDaylightTime(d) == dst) {
-                guess = tz;
-                break;
-            }
-        }
-
-        return guess;
-    }
-
     /** convert ServiceState registration code
      * to service state */
     private int regCodeToServiceState(int code) {
@@ -3586,100 +3565,59 @@
                     + " start=" + start + " delay=" + (start - nitzReceiveTime));
         }
 
+        NitzData nitzData = NitzData.parse(nitz);
+        if (nitzData == null) {
+            return;
+        }
+
         try {
-            /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
-             * offset as well (which we won't worry about until later) */
-            Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
-
-            c.clear();
-            c.set(Calendar.DST_OFFSET, 0);
-
-            String[] nitzSubs = nitz.split("[/:,+-]");
-
-            int year = 2000 + Integer.parseInt(nitzSubs[0]);
-            if (year > MAX_NITZ_YEAR) {
-                if (DBG) loge("NITZ year: " + year + " exceeds limit, skip NITZ time update");
-                return;
-            }
-            c.set(Calendar.YEAR, year);
-
-            // month is 0 based!
-            int month = Integer.parseInt(nitzSubs[1]) - 1;
-            c.set(Calendar.MONTH, month);
-
-            int date = Integer.parseInt(nitzSubs[2]);
-            c.set(Calendar.DATE, date);
-
-            int hour = Integer.parseInt(nitzSubs[3]);
-            c.set(Calendar.HOUR, hour);
-
-            int minute = Integer.parseInt(nitzSubs[4]);
-            c.set(Calendar.MINUTE, minute);
-
-            int second = Integer.parseInt(nitzSubs[5]);
-            c.set(Calendar.SECOND, second);
-
-            boolean sign = (nitz.indexOf('-') == -1);
-
-            int tzOffset = Integer.parseInt(nitzSubs[6]);
-
-            int dst = (nitzSubs.length >= 8 ) ? Integer.parseInt(nitzSubs[7]) : 0;
-
-            // The zone offset received from NITZ is for current local time,
-            // so DST correction is already applied.  Don't add it again.
-            //
-            // tzOffset += dst * 4;
-            //
-            // We could unapply it if we wanted the raw offset.
-
-            tzOffset = (sign ? 1 : -1) * tzOffset * 15 * 60 * 1000;
-
-            TimeZone    zone = null;
-
-            // As a special extension, the Android emulator appends the name of
-            // the host computer's timezone to the nitz string. this is zoneinfo
-            // timezone name of the form Area!Location or Area!Location!SubLocation
-            // so we need to convert the ! into /
-            if (nitzSubs.length >= 9) {
-                String  tzname = nitzSubs[8].replace('!','/');
-                zone = TimeZone.getTimeZone( tzname );
-            }
-
             String iso = ((TelephonyManager) mPhone.getContext().
                     getSystemService(Context.TELEPHONY_SERVICE)).
                     getNetworkCountryIsoForPhone(mPhone.getPhoneId());
 
+            TimeZone zone = nitzData.getEmulatorHostTimeZone();
             if (zone == null) {
-
                 if (mGotCountryCode) {
                     if (iso != null && iso.length() > 0) {
-                        zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
-                                c.getTimeInMillis(),
+                        zone = TimeUtils.getTimeZone(
+                                nitzData.getLocalOffsetMillis(),
+                                nitzData.isDst(),
+                                nitzData.getCurrentTimeInMillis(),
                                 iso);
                     } else {
                         // We don't have a valid iso country code.  This is
                         // most likely because we're on a test network that's
                         // using a bogus MCC (eg, "001"), so get a TimeZone
                         // based only on the NITZ parameters.
-                        zone = getNitzTimeZone(tzOffset, (dst != 0), c.getTimeInMillis());
+                        zone = NitzData.guessTimeZone(nitzData);
                     }
                 }
             }
 
-            if ((zone == null) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
+            int previousUtcOffset;
+            boolean previousIsDst;
+            if (mNitzData == null) {
+                // No previously saved NITZ data. Use the same defaults as Android would have done
+                // before it was possible to detect this case.
+                previousUtcOffset = 0;
+                previousIsDst = false;
+            } else {
+                previousUtcOffset = mNitzData.getLocalOffsetMillis();
+                previousIsDst = mNitzData.isDst();
+            }
+            if ((zone == null)
+                    || (nitzData.getLocalOffsetMillis() != previousUtcOffset)
+                    || (nitzData.isDst() != previousIsDst)) {
                 // We got the time before the country or the zone has changed
                 // so we don't know how to identify the DST rules yet.  Save
                 // the information and hope to fix it up later.
 
                 mNeedFixZoneAfterNitz = true;
-                mZoneOffset  = tzOffset;
-                mZoneDst     = dst != 0;
-                mZoneTime    = c.getTimeInMillis();
+                mNitzData = nitzData;
             }
 
-            String tmpLog = "NITZ: nitz=" + nitz + " nitzReceiveTime=" + nitzReceiveTime
-                    + " tzOffset=" + tzOffset + " dst=" + dst + " zone="
-                    + (zone != null ? zone.getID() : "NULL")
+            String tmpLog = "NITZ: nitzData=" + nitzData + " nitzReceiveTime=" + nitzReceiveTime
+                    + " zone=" + (zone != null ? zone.getID() : "NULL")
                     + " iso=" + iso + " mGotCountryCode=" + mGotCountryCode
                     + " mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz
                     + " getAutoTimeZone()=" + getAutoTimeZone();
@@ -3704,52 +3642,51 @@
             try {
                 mWakeLock.acquire();
 
+                long millisSinceNitzReceived = SystemClock.elapsedRealtime() - nitzReceiveTime;
+                if (millisSinceNitzReceived < 0) {
+                    // Sanity check: something is wrong
+                    if (DBG) {
+                        log("NITZ: not setting time, clock has rolled "
+                                + "backwards since NITZ time was received, "
+                                + nitz);
+                    }
+                    return;
+                }
+
+                if (millisSinceNitzReceived > Integer.MAX_VALUE) {
+                    // If the time is this far off, something is wrong > 24 days!
+                    if (DBG) {
+                        log("NITZ: not setting time, processing has taken "
+                                + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
+                                + " days");
+                    }
+                    return;
+                }
+
+                // Adjust the NITZ time by the delay since it was received.
+                long adjustedCurrentTimeMillis = nitzData.getCurrentTimeInMillis();
+                adjustedCurrentTimeMillis += millisSinceNitzReceived;
+
                 if (!mPhone.isPhoneTypeGsm() || getAutoTime()) {
-                    long millisSinceNitzReceived
-                            = SystemClock.elapsedRealtime() - nitzReceiveTime;
-
-                    if (millisSinceNitzReceived < 0) {
-                        // Sanity check: something is wrong
-                        if (DBG) {
-                            log("NITZ: not setting time, clock has rolled "
-                                    + "backwards since NITZ time was received, "
-                                    + nitz);
-                        }
-                        return;
-                    }
-
-                    if (millisSinceNitzReceived > Integer.MAX_VALUE) {
-                        // If the time is this far off, something is wrong > 24 days!
-                        if (DBG) {
-                            log("NITZ: not setting time, processing has taken "
-                                    + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
-                                    + " days");
-                        }
-                        return;
-                    }
-
-                    // Note: with range checks above, cast to int is safe
-                    c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
-
                     tmpLog = "NITZ: nitz=" + nitz + " nitzReceiveTime=" + nitzReceiveTime
-                            + " Setting time of day to " + c.getTime()
+                            + " Setting time of day to " + adjustedCurrentTimeMillis
                             + " NITZ receive delay(ms): " + millisSinceNitzReceived
                             + " gained(ms): "
-                            + (c.getTimeInMillis() - System.currentTimeMillis())
+                            + (adjustedCurrentTimeMillis - System.currentTimeMillis())
                             + " from " + nitz;
                     if (DBG) {
                         log(tmpLog);
                     }
                     mTimeLog.log(tmpLog);
                     if (mPhone.isPhoneTypeGsm()) {
-                        setAndBroadcastNetworkSetTime(c.getTimeInMillis());
+                        setAndBroadcastNetworkSetTime(adjustedCurrentTimeMillis);
                         Rlog.i(LOG_TAG, "NITZ: after Setting time of day");
                     } else {
                         if (getAutoTime()) {
                             /**
                              * Update system time automatically
                              */
-                            long gained = c.getTimeInMillis() - System.currentTimeMillis();
+                            long gained = adjustedCurrentTimeMillis - System.currentTimeMillis();
                             long timeSinceLastUpdate = SystemClock.elapsedRealtime() - mSavedAtTime;
                             int nitzUpdateSpacing = Settings.Global.getInt(mCr,
                                     Settings.Global.NITZ_UPDATE_SPACING, mNitzUpdateSpacing);
@@ -3759,12 +3696,13 @@
                             if ((mSavedAtTime == 0) || (timeSinceLastUpdate > nitzUpdateSpacing)
                                     || (Math.abs(gained) > nitzUpdateDiff)) {
                                 if (DBG) {
-                                    log("NITZ: Auto updating time of day to " + c.getTime()
+                                    log("NITZ: Auto updating time of day to "
+                                            + adjustedCurrentTimeMillis
                                             + " NITZ receive delay=" + millisSinceNitzReceived
                                             + "ms gained=" + gained + "ms from " + nitz);
                                 }
 
-                                setAndBroadcastNetworkSetTime(c.getTimeInMillis());
+                                setAndBroadcastNetworkSetTime(adjustedCurrentTimeMillis);
                             } else {
                                 if (DBG) {
                                     log("NITZ: ignore, a previous update was "
@@ -3775,8 +3713,8 @@
                         }
                     }
                 }
-                SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
-                saveNitzTime(c.getTimeInMillis());
+                SystemProperties.set("gsm.nitz.time", String.valueOf(adjustedCurrentTimeMillis));
+                saveNitzTime(adjustedCurrentTimeMillis);
                 mNitzUpdatedTime = true;
             } finally {
                 if (DBG) {
@@ -4443,6 +4381,7 @@
                 mSignalStrength.setGsm(isGsm);
             }
             mSignalStrength.setLteRsrpBoost(mSS.getLteEarfcnRsrpBoost());
+            mSignalStrength.setUseOnlyRsrpForLteLevel(isUseOnlyRsrpForLteLevel());
         } else {
             log("onSignalStrengthResult() Exception from RIL : " + ar.exception);
             mSignalStrength = new SignalStrength(isGsm);
@@ -4483,7 +4422,7 @@
      * @param needToFixTimeZone
      * @return true if time zone needs to be fixed
      */
-    protected boolean shouldFixTimeZoneNow(Phone phone, String operatorNumeric,
+    private boolean shouldFixTimeZoneNow(Phone phone, String operatorNumeric,
             String prevOperatorNumeric, boolean needToFixTimeZone) {
         // Return false if the mcc isn't valid as we don't know where we are.
         // Return true if we have an IccCard and the mcc changed or we
@@ -4702,9 +4641,7 @@
         pw.println(" mEmergencyOnly=" + mEmergencyOnly);
         pw.println(" mNeedFixZoneAfterNitz=" + mNeedFixZoneAfterNitz);
         pw.flush();
-        pw.println(" mZoneOffset=" + mZoneOffset);
-        pw.println(" mZoneDst=" + mZoneDst);
-        pw.println(" mZoneTime=" + mZoneTime);
+        pw.println(" mNitzData=" + mNitzData);
         pw.println(" mGotCountryCode=" + mGotCountryCode);
         pw.println(" mNitzUpdatedTime=" + mNitzUpdatedTime);
         pw.println(" mSavedTimeZone=" + mSavedTimeZone);
@@ -5085,4 +5022,25 @@
             }
         }
     }
+
+    /**
+     * Check whether to use only RSRP for the number of LTE signal bar.
+     *
+     * @return true if it should use only RSRP for the number of LTE signal bar.
+     */
+    private boolean isUseOnlyRsrpForLteLevel() {
+        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            // If an invalid subId is used, this bundle will contain default values.
+            PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
+            if (config != null) {
+                return config.getBoolean(
+                        CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL);
+            }
+        }
+        // Return static default defined in CarrierConfigManager.
+        return CarrierConfigManager.getDefaultConfig().getBoolean(
+                CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL);
+    }
 }
diff --git a/com/android/internal/telephony/SubscriptionController.java b/com/android/internal/telephony/SubscriptionController.java
index f122cc0..2629b42 100644
--- a/com/android/internal/telephony/SubscriptionController.java
+++ b/com/android/internal/telephony/SubscriptionController.java
@@ -45,6 +45,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IccCardConstants.State;
+import com.android.internal.telephony.uicc.IccUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -87,6 +88,7 @@
     static final boolean VDBG = false;
     static final boolean DBG_CACHE = false;
     static final int MAX_LOCAL_LOG_LINES = 500; // TODO: Reduce to 100 when 17678050 is fixed
+    private static final int DEPRECATED_SETTING = -1;
     private ScLocalLog mLocalLog = new ScLocalLog(MAX_LOCAL_LOG_LINES);
 
     /* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */
@@ -193,6 +195,7 @@
 
     protected SubscriptionController(Context c) {
         init(c);
+        migrateImsSettings();
     }
 
     protected void init(Context c) {
@@ -222,6 +225,8 @@
                 ServiceManager.addService("isub", this);
         }
 
+        migrateImsSettings();
+
         if (DBG) logdl("[SubscriptionController] init by Phone");
     }
 
@@ -905,8 +910,10 @@
             ContentResolver resolver = mContext.getContentResolver();
             Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
                     new String[]{SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
-                            SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE},
-                    SubscriptionManager.ICC_ID + "=?", new String[]{iccId}, null);
+                            SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE,
+                            SubscriptionManager.ICC_ID},
+                    SubscriptionManager.ICC_ID + "=?" + " OR " + SubscriptionManager.ICC_ID + "=?",
+                            new String[]{iccId, IccUtils.getDecimalSubstring(iccId)}, null);
 
             boolean setDisplayName = false;
             try {
@@ -918,6 +925,7 @@
                     int subId = cursor.getInt(0);
                     int oldSimInfoId = cursor.getInt(1);
                     int nameSource = cursor.getInt(2);
+                    String oldIccId = cursor.getString(3);
                     ContentValues value = new ContentValues();
 
                     if (slotIndex != oldSimInfoId) {
@@ -928,6 +936,11 @@
                         setDisplayName = true;
                     }
 
+                    if (oldIccId != null && oldIccId.length() < iccId.length()
+                            && (oldIccId.equals(IccUtils.getDecimalSubstring(iccId)))) {
+                        value.put(SubscriptionManager.ICC_ID, iccId);
+                    }
+
                     if (value.size() > 0) {
                         resolver.update(SubscriptionManager.CONTENT_URI, value,
                                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
@@ -1645,7 +1658,12 @@
         broadcastDefaultVoiceSubIdChanged(subId);
     }
 
-    private void broadcastDefaultVoiceSubIdChanged(int subId) {
+    /**
+     * Broadcast intent of ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED.
+     * @hide
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void broadcastDefaultVoiceSubIdChanged(int subId) {
         // Broadcast an Intent for default voice sub change
         if (DBG) logdl("[broadcastDefaultVoiceSubIdChanged] subId=" + subId);
         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
@@ -1660,7 +1678,7 @@
         int subId = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        if (VDBG) logd("[getDefaultVoiceSubId] subId=" + subId);
+        if (VDBG) slogd("[getDefaultVoiceSubId] subId=" + subId);
         return subId;
     }
 
@@ -1980,6 +1998,17 @@
         enforceModifyPhoneState("setSubscriptionProperty");
         final long token = Binder.clearCallingIdentity();
         ContentResolver resolver = mContext.getContentResolver();
+
+        setSubscriptionPropertyIntoContentResolver(subId, propKey, propValue, resolver);
+
+        // Refresh the Cache of Active Subscription Info List
+        refreshCachedActiveSubscriptionInfoList();
+
+        Binder.restoreCallingIdentity(token);
+    }
+
+    private static void setSubscriptionPropertyIntoContentResolver(
+            int subId, String propKey, String propValue, ContentResolver resolver) {
         ContentValues value = new ContentValues();
         switch (propKey) {
             case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
@@ -1994,21 +2023,22 @@
             case SubscriptionManager.CB_CHANNEL_50_ALERT:
             case SubscriptionManager.CB_CMAS_TEST_ALERT:
             case SubscriptionManager.CB_OPT_OUT_DIALOG:
+            case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
+            case SubscriptionManager.VT_IMS_ENABLED:
+            case SubscriptionManager.WFC_IMS_ENABLED:
+            case SubscriptionManager.WFC_IMS_MODE:
+            case SubscriptionManager.WFC_IMS_ROAMING_MODE:
+            case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
                 value.put(propKey, Integer.parseInt(propValue));
                 break;
             default:
-                if(DBG) logd("Invalid column name");
+                if (DBG) slogd("Invalid column name");
                 break;
         }
 
         resolver.update(SubscriptionManager.CONTENT_URI, value,
                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
                         "=" + Integer.toString(subId), null);
-
-        // Refresh the Cache of Active Subscription Info List
-        refreshCachedActiveSubscriptionInfoList();
-
-        Binder.restoreCallingIdentity(token);
     }
 
     /**
@@ -2046,6 +2076,12 @@
                         case SubscriptionManager.CB_CHANNEL_50_ALERT:
                         case SubscriptionManager.CB_CMAS_TEST_ALERT:
                         case SubscriptionManager.CB_OPT_OUT_DIALOG:
+                        case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
+                        case SubscriptionManager.VT_IMS_ENABLED:
+                        case SubscriptionManager.WFC_IMS_ENABLED:
+                        case SubscriptionManager.WFC_IMS_MODE:
+                        case SubscriptionManager.WFC_IMS_ROAMING_MODE:
+                        case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
                             resultValue = cursor.getInt(0) + "";
                             break;
                         default:
@@ -2139,4 +2175,47 @@
             Binder.restoreCallingIdentity(token);
         }
     }
+
+    /**
+     * Migrating Ims settings from global setting to subscription DB, if not already done.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void migrateImsSettings() {
+        migrateImsSettingHelper(
+                Settings.Global.ENHANCED_4G_MODE_ENABLED,
+                SubscriptionManager.ENHANCED_4G_MODE_ENABLED);
+        migrateImsSettingHelper(
+                Settings.Global.VT_IMS_ENABLED,
+                SubscriptionManager.VT_IMS_ENABLED);
+        migrateImsSettingHelper(
+                Settings.Global.WFC_IMS_ENABLED,
+                SubscriptionManager.WFC_IMS_ENABLED);
+        migrateImsSettingHelper(
+                Settings.Global.WFC_IMS_MODE,
+                SubscriptionManager.WFC_IMS_MODE);
+        migrateImsSettingHelper(
+                Settings.Global.WFC_IMS_ROAMING_MODE,
+                SubscriptionManager.WFC_IMS_ROAMING_MODE);
+        migrateImsSettingHelper(
+                Settings.Global.WFC_IMS_ROAMING_ENABLED,
+                SubscriptionManager.WFC_IMS_ROAMING_ENABLED);
+    }
+
+    private void migrateImsSettingHelper(String settingGlobal, String subscriptionProperty) {
+        ContentResolver resolver = mContext.getContentResolver();
+        int defaultSubId = getDefaultVoiceSubId();
+        try {
+            int prevSetting = Settings.Global.getInt(resolver, settingGlobal);
+
+            if (prevSetting != DEPRECATED_SETTING) {
+                // Write previous setting into Subscription DB.
+                setSubscriptionPropertyIntoContentResolver(defaultSubId, subscriptionProperty,
+                        Integer.toString(prevSetting), resolver);
+                // Write global setting value with DEPRECATED_SETTING making sure
+                // migration only happen once.
+                Settings.Global.putInt(resolver, settingGlobal, DEPRECATED_SETTING);
+            }
+        } catch (Settings.SettingNotFoundException e) {
+        }
+    }
 }
diff --git a/com/android/internal/telephony/SubscriptionInfoUpdater.java b/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 2c819c3..1a2e09c 100644
--- a/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -273,7 +273,7 @@
                 if (ar.exception == null) {
                     if (ar.result != null) {
                         byte[] data = (byte[])ar.result;
-                        mIccId[slotId] = IccUtils.bcdToString(data, 0, data.length);
+                        mIccId[slotId] = IccUtils.bchToString(data, 0, data.length);
                     } else {
                         logd("Null ar");
                         mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
@@ -399,11 +399,11 @@
             logd("handleSimLoaded: IccRecords null");
             return;
         }
-        if (records.getIccId() == null) {
-            logd("handleSimLoaded: IccID null");
+        if (records.getFullIccId() == null) {
+            logd("onRecieve: IccID null");
             return;
         }
-        mIccId[slotId] = records.getIccId();
+        mIccId[slotId] = records.getFullIccId();
 
         if (isAllIccIdQueryDone()) {
             updateSubscriptionInfoByIccId();
@@ -427,20 +427,12 @@
                 ContentResolver contentResolver = mContext.getContentResolver();
 
                 if (msisdn != null) {
-                    ContentValues number = new ContentValues(1);
-                    number.put(SubscriptionManager.NUMBER, msisdn);
-                    contentResolver.update(SubscriptionManager.CONTENT_URI, number,
-                            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
-                                    + Long.toString(subId), null);
-
-                    // refresh Cached Active Subscription Info List
-                    SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
+                    SubscriptionController.getInstance().setDisplayNumber(msisdn, subId);
                 }
 
                 SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
                 String nameToSet;
                 String simCarrierName = tm.getSimOperatorName(subId);
-                ContentValues name = new ContentValues(1);
 
                 if (subInfo != null && subInfo.getNameSource() !=
                         SubscriptionManager.NAME_SOURCE_USER_INPUT) {
@@ -449,14 +441,8 @@
                     } else {
                         nameToSet = "CARD " + Integer.toString(slotId + 1);
                     }
-                    name.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
                     logd("sim name = " + nameToSet);
-                    contentResolver.update(SubscriptionManager.CONTENT_URI, name,
-                            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
-                                    + "=" + Long.toString(subId), null);
-
-                    // refresh Cached Active Subscription Info List
-                    SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList();
+                    SubscriptionController.getInstance().setDisplayName(nameToSet, subId);
                 }
 
                 /* Update preferred network type and network selection mode on SIM change.
@@ -569,16 +555,19 @@
 
         ContentResolver contentResolver = mContext.getContentResolver();
         String[] oldIccId = new String[PROJECT_SIM_NUM];
+        String[] decIccId = new String[PROJECT_SIM_NUM];
         for (int i = 0; i < PROJECT_SIM_NUM; i++) {
             oldIccId[i] = null;
             List<SubscriptionInfo> oldSubInfo =
                     SubscriptionController.getInstance().getSubInfoUsingSlotIndexWithCheck(i, false,
                     mContext.getOpPackageName());
+            decIccId[i] = IccUtils.getDecimalSubstring(mIccId[i]);
             if (oldSubInfo != null && oldSubInfo.size() > 0) {
                 oldIccId[i] = oldSubInfo.get(0).getIccId();
                 logd("updateSubscriptionInfoByIccId: oldSubId = "
                         + oldSubInfo.get(0).getSubscriptionId());
-                if (mInsertSimState[i] == SIM_NOT_CHANGE && !mIccId[i].equals(oldIccId[i])) {
+                if (mInsertSimState[i] == SIM_NOT_CHANGE && !(mIccId[i].equals(oldIccId[i])
+                            || (decIccId[i] != null && decIccId[i].equals(oldIccId[i])))) {
                     mInsertSimState[i] = SIM_CHANGED;
                 }
                 if (mInsertSimState[i] != SIM_NOT_CHANGE) {
@@ -623,7 +612,7 @@
                 } else /*if (sInsertSimState[i] != SIM_NOT_INSERT)*/ {
                     mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i], i);
                 }
-                if (isNewSim(mIccId[i], oldIccId)) {
+                if (isNewSim(mIccId[i], decIccId[i], oldIccId)) {
                     nNewCardCount++;
                     switch (i) {
                         case PhoneConstants.SUB1:
@@ -798,12 +787,15 @@
         return -1;
     }
 
-    private boolean isNewSim(String iccId, String[] oldIccId) {
+    private boolean isNewSim(String iccId, String decIccId, String[] oldIccId) {
         boolean newSim = true;
         for(int i = 0; i < PROJECT_SIM_NUM; i++) {
             if(iccId.equals(oldIccId[i])) {
                 newSim = false;
                 break;
+            } else if (decIccId != null && decIccId.equals(oldIccId[i])) {
+                newSim = false;
+                break;
             }
         }
         logd("newSim = " + newSim);
diff --git a/com/android/internal/telephony/UiccSmsController.java b/com/android/internal/telephony/UiccSmsController.java
index e6cb972..59449b8 100644
--- a/com/android/internal/telephony/UiccSmsController.java
+++ b/com/android/internal/telephony/UiccSmsController.java
@@ -159,6 +159,21 @@
         }
     }
 
+    @Override
+    public void sendTextForSubscriberWithOptions(int subId, String callingPackage,
+            String destAddr, String scAddr, String parts, PendingIntent sentIntents,
+            PendingIntent deliveryIntents, boolean persistMessage, int priority,
+            boolean expectMore, int validityPeriod) {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null ) {
+            iccSmsIntMgr.sendTextWithOptions(callingPackage, destAddr, scAddr, parts, sentIntents,
+                    deliveryIntents, persistMessage,  priority, expectMore, validityPeriod);
+        } else {
+            Rlog.e(LOG_TAG,"sendTextWithOptions iccSmsIntMgr is null for" +
+                          " Subscription: " + subId);
+        }
+    }
+
     public void sendMultipartText(String callingPackage, String destAddr, String scAddr,
             List<String> parts, List<PendingIntent> sentIntents,
             List<PendingIntent> deliveryIntents) throws android.os.RemoteException {
@@ -184,6 +199,22 @@
     }
 
     @Override
+    public void sendMultipartTextForSubscriberWithOptions(int subId, String callingPackage,
+            String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
+            List<PendingIntent> deliveryIntents, boolean persistMessage, int priority,
+            boolean expectMore, int validityPeriod) {
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        if (iccSmsIntMgr != null ) {
+            iccSmsIntMgr.sendMultipartTextWithOptions(callingPackage, destAddr, scAddr, parts,
+                    sentIntents, deliveryIntents, persistMessage,  priority, expectMore,
+                    validityPeriod);
+        } else {
+            Rlog.e(LOG_TAG,"sendMultipartTextWithOptions iccSmsIntMgr is null for" +
+                          " Subscription: " + subId);
+        }
+    }
+
+    @Override
     public boolean enableCellBroadcastForSubscriber(int subId, int messageIdentifier, int ranType)
                 throws android.os.RemoteException {
         return enableCellBroadcastRangeForSubscriber(subId, messageIdentifier, messageIdentifier,
diff --git a/com/android/internal/telephony/cat/CommandParamsFactory.java b/com/android/internal/telephony/cat/CommandParamsFactory.java
index eb92888..256f541 100644
--- a/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -28,16 +28,16 @@
 import java.util.List;
 import java.util.Locale;
 
-import static com.android.internal.telephony.cat.CatCmdMessage.
-                   SetupEventListConstants.USER_ACTIVITY_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.
-                   SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.
-                   SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.
-                   SetupEventListConstants.BROWSER_TERMINATION_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.
-                   SetupEventListConstants.BROWSING_STATUS_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+                   .SetupEventListConstants.BROWSER_TERMINATION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+                   .SetupEventListConstants.BROWSING_STATUS_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+                   .SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+                   .SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage
+                   .SetupEventListConstants.USER_ACTIVITY_EVENT;
 /**
  * Factory class, used for decoding raw byte arrays, received from baseband,
  * into a CommandParams object.
@@ -917,6 +917,10 @@
         ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID, ctlvs);
         if (ctlv != null) {
             textMsg.text = ValueParser.retrieveAlphaId(ctlv);
+            // Assign the tone message text to empty string, if alpha identifier
+            // data is null. If no alpha identifier tlv is present, then tone
+            // message text will be null.
+            if (textMsg.text == null) textMsg.text = "";
         }
         // parse tone duration
         ctlv = searchForTag(ComprehensionTlvTag.DURATION, ctlvs);
diff --git a/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index 1cfdc33..080ca1c 100644
--- a/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -113,7 +113,7 @@
         if (pdu != null) {
             HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu);
             SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
-                    null /*messageUri*/, false /*isExpectMore*/, null /*fullMessageText*/,
+                    null /*messageUri*/, false /*expectMore*/, null /*fullMessageText*/,
                     false /*isText*/, true /*persistMessage*/);
 
             String carrierPackage = getCarrierAppPackageName();
@@ -141,13 +141,14 @@
     @Override
     public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
             PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
-            boolean persistMessage) {
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
-                scAddr, destAddr, text, (deliveryIntent != null), null);
+                scAddr, destAddr, text, (deliveryIntent != null), null, priority);
         if (pdu != null) {
             HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
             SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
-                    messageUri, false /*isExpectMore*/, text, true /*isText*/, persistMessage);
+                    messageUri, expectMore, text, true /*isText*/, persistMessage,
+                    priority, validityPeriod);
 
             String carrierPackage = getCarrierAppPackageName();
             if (carrierPackage != null) {
@@ -189,7 +190,7 @@
             String message, SmsHeader smsHeader, int encoding,
             PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
-            String fullMessageText) {
+            String fullMessageText, int priority, boolean expectMore, int validityPeriod) {
         UserData uData = new UserData();
         uData.payloadStr = message;
         uData.userDataHeader = smsHeader;
@@ -205,14 +206,14 @@
          * callback to the sender when that last fragment delivery
          * has been acknowledged. */
         SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
-                uData, (deliveryIntent != null) && lastPart);
+                uData, (deliveryIntent != null) && lastPart, priority);
 
         HashMap map = getSmsTrackerMap(destinationAddress, scAddress,
                 message, submitPdu);
         return getSmsTracker(map, sentIntent, deliveryIntent,
                 getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader,
-                false /*isExpextMore*/, fullMessageText, true /*isText*/,
-                true /*persistMessage*/);
+                (!lastPart || expectMore), fullMessageText, true /*isText*/,
+                true /*persistMessage*/, priority, validityPeriod);
     }
 
     @Override
diff --git a/com/android/internal/telephony/cdma/SmsMessage.java b/com/android/internal/telephony/cdma/SmsMessage.java
index 7a53ef6..14c5f4b 100644
--- a/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/com/android/internal/telephony/cdma/SmsMessage.java
@@ -99,6 +99,15 @@
     private static final int RETURN_NO_ACK  = 0;
     private static final int RETURN_ACK     = 1;
 
+    /**
+     * Supported priority modes for CDMA SMS messages
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+     */
+    private static final int PRIORITY_NORMAL        = 0x0;
+    private static final int PRIORITY_INTERACTIVE   = 0x1;
+    private static final int PRIORITY_URGENT        = 0x2;
+    private static final int PRIORITY_EMERGENCY     = 0x3;
+
     private SmsEnvelope mEnvelope;
     private BearerData mBearerData;
 
@@ -211,6 +220,26 @@
      */
     public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
             boolean statusReportRequested, SmsHeader smsHeader) {
+        return getSubmitPdu(scAddr, destAddr, message, statusReportRequested, smsHeader, -1);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message
+     *
+     * @param scAddr                Service Centre address.  Null means use default.
+     * @param destAddr              Address of the recipient.
+     * @param message               String representation of the message payload.
+     * @param statusReportRequested Indicates whether a report is requested for this message.
+     * @param smsHeader             Array containing the data for the User Data Header, preceded
+     *                              by the Element Identifiers.
+     * @param priority              Priority level of the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
+            boolean statusReportRequested, SmsHeader smsHeader, int priority) {
 
         /**
          * TODO(cleanup): Do we really want silent failure like this?
@@ -224,7 +253,7 @@
         UserData uData = new UserData();
         uData.payloadStr = message;
         uData.userDataHeader = smsHeader;
-        return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+        return privateGetSubmitPdu(destAddr, statusReportRequested, uData, priority);
     }
 
     /**
@@ -282,6 +311,22 @@
     }
 
     /**
+     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+     *
+     * @param destAddr the address of the destination for the message
+     * @param userData the data for the message
+     * @param statusReportRequested Indicates whether a report is requested for this message.
+     * @param priority Priority level of the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
+            boolean statusReportRequested, int priority) {
+        return privateGetSubmitPdu(destAddr, statusReportRequested, userData, priority);
+    }
+
+    /**
      * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
      */
     @Override
@@ -764,6 +809,15 @@
      */
     private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
             UserData userData) {
+        return privateGetSubmitPdu(destAddrStr, statusReportRequested, userData, -1);
+    }
+
+    /**
+     * Creates BearerData and Envelope from parameters for a Submit SMS.
+     * @return byte stream for SubmitPdu.
+     */
+    private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+            UserData userData, int priority) {
 
         /**
          * TODO(cleanup): give this function a more meaningful name.
@@ -792,6 +846,10 @@
         bearerData.userAckReq = false;
         bearerData.readAckReq = false;
         bearerData.reportReq = false;
+        if (priority >= PRIORITY_NORMAL && priority <= PRIORITY_EMERGENCY) {
+            bearerData.priorityIndicatorSet = true;
+            bearerData.priority = priority;
+        }
 
         bearerData.userData = userData;
 
diff --git a/com/android/internal/telephony/dataconnection/ApnContext.java b/com/android/internal/telephony/dataconnection/ApnContext.java
index 3cd804d..1b42d4a 100644
--- a/com/android/internal/telephony/dataconnection/ApnContext.java
+++ b/com/android/internal/telephony/dataconnection/ApnContext.java
@@ -35,7 +35,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -272,7 +271,10 @@
             log("setState: " + s + ", previous state:" + mState);
         }
 
-        mState = s;
+        if (mState != s) {
+            mStateLocalLog.log("State changed from " + mState + " to " + s);
+            mState = s;
+        }
 
         if (mState == DctConstants.State.FAILED) {
             if (mRetryManager.getWaitingApns() != null) {
@@ -399,8 +401,7 @@
 
     private final ArrayList<LocalLog> mLocalLogs = new ArrayList<>();
     private final ArrayList<NetworkRequest> mNetworkRequests = new ArrayList<>();
-    private final ArrayDeque<LocalLog> mHistoryLogs = new ArrayDeque<>();
-    private final static int MAX_HISTORY_LOG_COUNT = 4;
+    private final LocalLog mStateLocalLog = new LocalLog(50);
 
     public void requestLog(String str) {
         synchronized (mRefCountLock) {
@@ -719,13 +720,15 @@
             pw.increaseIndent();
             for (LocalLog l : mLocalLogs) {
                 l.dump(fd, pw, args);
-            }
-            if (mHistoryLogs.size() > 0) pw.println("Historical Logs:");
-            for (LocalLog l : mHistoryLogs) {
-                l.dump(fd, pw, args);
+                pw.println("-----");
             }
             pw.decreaseIndent();
+            pw.println("Historical APN state:");
+            pw.increaseIndent();
+            mStateLocalLog.dump(fd, pw, args);
+            pw.decreaseIndent();
             pw.println(mRetryManager);
+            pw.println("--------------------------");
         }
     }
 }
diff --git a/com/android/internal/telephony/dataconnection/DataConnection.java b/com/android/internal/telephony/dataconnection/DataConnection.java
index 14b2c62..9e4b29c 100644
--- a/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -170,7 +170,7 @@
     private int mDataRegState = Integer.MAX_VALUE;
     private NetworkInfo mNetworkInfo;
     private NetworkAgent mNetworkAgent;
-    private LocalLog mLocalLog = new LocalLog(50);
+    private LocalLog mNetCapsLocalLog = new LocalLog(50);
 
     int mTag;
     public int mCid;
@@ -976,6 +976,12 @@
 
         result.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(mPhone.getSubId())));
 
+        if (!mPhone.getServiceState().getDataRoaming()) {
+            result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+        } else {
+            result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
+        }
+
         return result;
     }
 
@@ -1213,26 +1219,22 @@
                                 + " drs=" + mDataRegState
                                 + " mRilRat=" + mRilRat);
                     }
-                    ServiceState ss = mPhone.getServiceState();
-                    int networkType = ss.getDataNetworkType();
-                    mNetworkInfo.setSubtype(networkType,
-                            TelephonyManager.getNetworkTypeName(networkType));
+                    updateNetworkInfo();
+                    updateNetworkInfoSuspendState();
                     if (mNetworkAgent != null) {
-                        updateNetworkInfoSuspendState();
                         mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
                         mNetworkAgent.sendNetworkInfo(mNetworkInfo);
                         mNetworkAgent.sendLinkProperties(mLinkProperties);
                     }
                     break;
-
                 case EVENT_DATA_CONNECTION_ROAM_ON:
-                    mNetworkInfo.setRoaming(true);
-                    break;
-
                 case EVENT_DATA_CONNECTION_ROAM_OFF:
-                    mNetworkInfo.setRoaming(false);
+                    updateNetworkInfo();
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
+                        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+                    }
                     break;
-
                 default:
                     if (DBG) {
                         log("DcDefaultState: shouldn't happen but ignore msg.what="
@@ -1245,9 +1247,14 @@
         }
     }
 
-    private boolean updateNetworkInfoSuspendState() {
-        final NetworkInfo.DetailedState oldState = mNetworkInfo.getDetailedState();
+    private void updateNetworkInfo() {
+        final ServiceState state = mPhone.getServiceState();
+        final int subtype = state.getDataNetworkType();
+        mNetworkInfo.setSubtype(subtype, TelephonyManager.getNetworkTypeName(subtype));
+        mNetworkInfo.setRoaming(state.getDataRoaming());
+    }
 
+    private void updateNetworkInfoSuspendState() {
         // this is only called when we are either connected or suspended.  Decide which.
         if (mNetworkAgent == null) {
             Rlog.e(getName(), "Setting suspend state without a NetworkAgent");
@@ -1265,13 +1272,12 @@
                 if (ct.getState() != PhoneConstants.State.IDLE) {
                     mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.SUSPENDED, null,
                             mNetworkInfo.getExtraInfo());
-                    return (oldState != NetworkInfo.DetailedState.SUSPENDED);
+                    return;
                 }
             }
             mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null,
                     mNetworkInfo.getExtraInfo());
         }
-        return (oldState != mNetworkInfo.getDetailedState());
     }
 
     private DcDefaultState mDefaultState = new DcDefaultState();
@@ -1542,22 +1548,7 @@
         @Override public void enter() {
             if (DBG) log("DcActiveState: enter dc=" + DataConnection.this);
 
-            // verify and get updated information in case these things
-            // are obsolete
-            ServiceState ss = mPhone.getServiceState();
-            final int networkType = ss.getDataNetworkType();
-            if (mNetworkInfo.getSubtype() != networkType) {
-                log("DcActiveState with incorrect subtype (" + mNetworkInfo.getSubtype()
-                        + ", " + networkType + "), updating.");
-            }
-            mNetworkInfo.setSubtype(networkType, TelephonyManager.getNetworkTypeName(networkType));
-            final boolean roaming = ss.getDataRoaming();
-            if (roaming != mNetworkInfo.isRoaming()) {
-                log("DcActiveState with incorrect roaming (" + mNetworkInfo.isRoaming()
-                        + ", " + roaming + "), updating.");
-            }
-
-            mNetworkInfo.setRoaming(roaming);
+            updateNetworkInfo();
 
             // If we were retrying there maybe more than one, otherwise they'll only be one.
             notifyAllOfConnected(Phone.REASON_CONNECTED);
@@ -1685,17 +1676,11 @@
                     retVal = HANDLED;
                     break;
                 }
-                case EVENT_DATA_CONNECTION_ROAM_ON: {
-                    mNetworkInfo.setRoaming(true);
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
+                case EVENT_DATA_CONNECTION_ROAM_ON:
                 case EVENT_DATA_CONNECTION_ROAM_OFF: {
-                    mNetworkInfo.setRoaming(false);
+                    updateNetworkInfo();
                     if (mNetworkAgent != null) {
+                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
                         mNetworkAgent.sendNetworkInfo(mNetworkInfo);
                     }
                     retVal = HANDLED;
@@ -1721,8 +1706,10 @@
                 }
                 case EVENT_DATA_CONNECTION_VOICE_CALL_STARTED:
                 case EVENT_DATA_CONNECTION_VOICE_CALL_ENDED: {
-                    if (updateNetworkInfoSuspendState() && mNetworkAgent != null) {
-                        // state changed
+                    updateNetworkInfo();
+                    updateNetworkInfoSuspendState();
+                    if (mNetworkAgent != null) {
+                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities());
                         mNetworkAgent.sendNetworkInfo(mNetworkInfo);
                     }
                     retVal = HANDLED;
@@ -1844,7 +1831,7 @@
         public DcNetworkAgent(Looper l, Context c, String TAG, NetworkInfo ni,
                 NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
             super(l, c, TAG, ni, nc, lp, score, misc);
-            mLocalLog.log("New network agent created. capabilities=" + nc);
+            mNetCapsLocalLog.log("New network agent created. capabilities=" + nc);
             mNetworkCapabilities = nc;
         }
 
@@ -1897,7 +1884,7 @@
                         + mPhone.getServiceState().getRilDataRadioTechnology()
                         + ", DUN APN=\"" + mDct.fetchDunApn() + "\""
                         + ", mApnSetting=" + mApnSetting;
-                mLocalLog.log(logStr);
+                mNetCapsLocalLog.log(logStr);
                 log(logStr);
                 mNetworkCapabilities = networkCapabilities;
             }
@@ -2140,7 +2127,7 @@
         pw.println("mAc=" + mAc);
         pw.println("Network capabilities changed history:");
         pw.increaseIndent();
-        mLocalLog.dump(fd, pw, args);
+        mNetCapsLocalLog.dump(fd, pw, args);
         pw.decreaseIndent();
         pw.decreaseIndent();
         pw.println();
diff --git a/com/android/internal/telephony/euicc/EuiccController.java b/com/android/internal/telephony/euicc/EuiccController.java
index 0d58d80..ac2a039 100644
--- a/com/android/internal/telephony/euicc/EuiccController.java
+++ b/com/android/internal/telephony/euicc/EuiccController.java
@@ -411,6 +411,15 @@
                                                 callingToken, subscription, switchAfterDownload,
                                                 callingPackage));
                                 break;
+                            case EuiccService.RESULT_NEED_CONFIRMATION_CODE:
+                                resultCode = RESOLVABLE_ERROR;
+                                addResolutionIntent(extrasIntent,
+                                        EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE,
+                                        callingPackage,
+                                        EuiccOperation.forDownloadConfirmationCode(
+                                                callingToken, subscription, switchAfterDownload,
+                                                callingPackage));
+                                break;
                             default:
                                 resultCode = ERROR;
                                 extrasIntent.putExtra(
diff --git a/com/android/internal/telephony/euicc/EuiccOperation.java b/com/android/internal/telephony/euicc/EuiccOperation.java
index 3b0dbc5..84df52e 100644
--- a/com/android/internal/telephony/euicc/EuiccOperation.java
+++ b/com/android/internal/telephony/euicc/EuiccOperation.java
@@ -25,6 +25,7 @@
 import android.service.euicc.EuiccService;
 import android.telephony.euicc.DownloadableSubscription;
 import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -60,6 +61,7 @@
             ACTION_GET_METADATA_DEACTIVATE_SIM,
             ACTION_DOWNLOAD_DEACTIVATE_SIM,
             ACTION_DOWNLOAD_NO_PRIVILEGES,
+            ACTION_DOWNLOAD_CONFIRMATION_CODE,
     })
     @interface Action {}
 
@@ -75,6 +77,8 @@
     static final int ACTION_SWITCH_DEACTIVATE_SIM = 5;
     @VisibleForTesting
     static final int ACTION_SWITCH_NO_PRIVILEGES = 6;
+    @VisibleForTesting
+    static final int ACTION_DOWNLOAD_CONFIRMATION_CODE = 7;
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public final @Action int mAction;
@@ -123,6 +127,17 @@
                 subscription,  0 /* subscriptionId */, switchAfterDownload, callingPackage);
     }
 
+    /**
+     * {@link EuiccManager#downloadSubscription} failed with
+     * {@link EuiccService#RESULT_NEED_CONFIRMATION_CODE} error.
+     */
+    public static EuiccOperation forDownloadConfirmationCode(long callingToken,
+            DownloadableSubscription subscription, boolean switchAfterDownload,
+            String callingPackage) {
+        return new EuiccOperation(ACTION_DOWNLOAD_CONFIRMATION_CODE, callingToken,
+                subscription, 0 /* subscriptionId */, switchAfterDownload, callingPackage);
+    }
+
     static EuiccOperation forGetDefaultListDeactivateSim(long callingToken, String callingPackage) {
         return new EuiccOperation(ACTION_GET_DEFAULT_LIST_DEACTIVATE_SIM, callingToken,
                 null /* downloadableSubscription */, 0 /* subscriptionId */,
@@ -205,6 +220,11 @@
                         resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT),
                         callbackIntent);
                 break;
+            case ACTION_DOWNLOAD_CONFIRMATION_CODE:
+                resolvedDownloadConfirmationCode(
+                        resolutionExtras.getString(EuiccService.RESOLUTION_EXTRA_CONFIRMATION_CODE),
+                        callbackIntent);
+                break;
             case ACTION_GET_DEFAULT_LIST_DEACTIVATE_SIM:
                 resolvedGetDefaultListDeactivateSim(
                         resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT),
@@ -284,6 +304,24 @@
         }
     }
 
+    private void resolvedDownloadConfirmationCode(String confirmationCode,
+            PendingIntent callbackIntent) {
+        if (TextUtils.isEmpty(confirmationCode)) {
+            fail(callbackIntent);
+        } else {
+            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+                mDownloadableSubscription.setConfirmationCode(confirmationCode);
+            }
+            EuiccController.get()
+                    .downloadSubscription(
+                            mDownloadableSubscription,
+                            mSwitchAfterDownload,
+                            mCallingPackage,
+                            true /* forceDeactivateSim */,
+                            callbackIntent);
+        }
+    }
+
     private void resolvedGetDefaultListDeactivateSim(
             boolean consent, PendingIntent callbackIntent) {
         if (consent) {
diff --git a/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 8f18c61..6e4a79f 100644
--- a/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -157,7 +157,7 @@
         if (pdu != null) {
             HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu);
             SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
-                    null /*messageUri*/, false /*isExpectMore*/, null /*fullMessageText*/,
+                    null /*messageUri*/, false /*expectMore*/, null /*fullMessageText*/,
                     false /*isText*/, true /*persistMessage*/);
 
             String carrierPackage = getCarrierAppPackageName();
@@ -179,14 +179,14 @@
     @Override
     public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
             PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
-            boolean persistMessage) {
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
-                scAddr, destAddr, text, (deliveryIntent != null));
+                scAddr, destAddr, text, (deliveryIntent != null), validityPeriod);
         if (pdu != null) {
             HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
             SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
-                    messageUri, false /*isExpectMore*/, text /*fullMessageText*/, true /*isText*/,
-                    persistMessage);
+                    messageUri, false /*expectMore*/, text /*fullMessageText*/, true /*isText*/,
+                    persistMessage, priority, validityPeriod);
 
             String carrierPackage = getCarrierAppPackageName();
             if (carrierPackage != null) {
@@ -221,17 +221,17 @@
             String message, SmsHeader smsHeader, int encoding,
             PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
-            String fullMessageText) {
+            String fullMessageText, int priority, boolean expectMore, int validityPeriod) {
         SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
                 message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
-                encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
+                encoding, smsHeader.languageTable, smsHeader.languageShiftTable, validityPeriod);
         if (pdu != null) {
             HashMap map =  getSmsTrackerMap(destinationAddress, scAddress,
                     message, pdu);
             return getSmsTracker(map, sentIntent,
                     deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri,
-                    smsHeader, !lastPart, fullMessageText, true /*isText*/,
-                    false /*persistMessage*/);
+                    smsHeader, (!lastPart || expectMore), fullMessageText, true /*isText*/,
+                    false /*persistMessage*/, priority, validityPeriod);
         } else {
             Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
             return null;
diff --git a/com/android/internal/telephony/gsm/SmsMessage.java b/com/android/internal/telephony/gsm/SmsMessage.java
index 1ca19e0..4f5bfa9 100644
--- a/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/com/android/internal/telephony/gsm/SmsMessage.java
@@ -89,6 +89,18 @@
 
     private int mVoiceMailCount = 0;
 
+    private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00;
+    private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01;
+    private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02;
+    private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03;
+
+    //Validity Period min - 5 mins
+    private static final int VALIDITY_PERIOD_MIN = 5;
+    //Validity Period max - 63 weeks
+    private static final int VALIDITY_PERIOD_MAX = 635040;
+
+    private static final int INVALID_VALIDITY_PERIOD = -1;
+
     public static class SubmitPdu extends SubmitPduBase {
     }
 
@@ -202,6 +214,45 @@
     }
 
     /**
+     * Get Encoded Relative Validty Period Value from Validity period in mins.
+     *
+     * @param validityPeriod Validity period in mins.
+     *
+     * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     * ||relValidityPeriod (TP-VP)  ||                 ||  validityPeriod   ||
+     *
+     *      0 to 143                            --->       (TP-VP + 1) x 5 minutes
+     *
+     *      144 to 167                         --->        12 hours + ((TP-VP -143) x 30 minutes)
+     *
+     *      168 to 196                         --->        (TP-VP - 166) x 1 day
+     *
+     *      197 to 255                         --->        (TP-VP - 192) x 1 week
+     *
+     * @return relValidityPeriod Encoded Relative Validity Period Value.
+     * @hide
+     */
+    public static int getRelativeValidityPeriod(int validityPeriod) {
+        int relValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+        if (validityPeriod < VALIDITY_PERIOD_MIN  || validityPeriod > VALIDITY_PERIOD_MAX) {
+            Rlog.e(LOG_TAG,"Invalid Validity Period" + validityPeriod);
+            return relValidityPeriod;
+        }
+
+        if (validityPeriod <= 720) {
+            relValidityPeriod = (validityPeriod  / 5) - 1;
+        } else if (validityPeriod <= 1440) {
+            relValidityPeriod = ((validityPeriod - 720) / 30) + 143;
+        } else if (validityPeriod <= 43200) {
+            relValidityPeriod = (validityPeriod  / 1440) + 166;
+        } else if (validityPeriod <= 635040) {
+            relValidityPeriod = (validityPeriod  / 10080) + 192;
+        }
+        return relValidityPeriod;
+    }
+
+    /**
      * Get an SMS-SUBMIT PDU for a destination address and a message
      *
      * @param scAddress Service Centre address.  Null means use default.
@@ -236,6 +287,29 @@
             String destinationAddress, String message,
             boolean statusReportRequested, byte[] header, int encoding,
             int languageTable, int languageShiftTable) {
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+            header, encoding, languageTable, languageShiftTable, -1);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message using the
+     * specified encoding.
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @param encoding Encoding defined by constants in
+     *        com.android.internal.telephony.SmsConstants.ENCODING_*
+     * @param languageTable
+     * @param languageShiftTable
+     * @param validityPeriod Validity Period of the message in Minutes.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested, byte[] header, int encoding,
+            int languageTable, int languageShiftTable, int validityPeriod) {
 
         // Perform null parameter checks.
         if (message == null || destinationAddress == null) {
@@ -272,8 +346,19 @@
         }
 
         SubmitPdu ret = new SubmitPdu();
-        // MTI = SMS-SUBMIT, UDHI = header != null
-        byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+
+        int validityPeriodFormat = VALIDITY_PERIOD_FORMAT_NONE;
+        int relativeValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+        // TP-Validity-Period-Format (TP-VPF) in 3GPP TS 23.040 V6.8.1 section 9.2.3.3
+        //bit 4:3 = 10 - TP-VP field present - relative format
+        if((relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod)) >= 0) {
+            validityPeriodFormat = VALIDITY_PERIOD_FORMAT_RELATIVE;
+        }
+
+        byte mtiByte = (byte)(0x01 | (validityPeriodFormat << 0x03) |
+                (header != null ? 0x40 : 0x00));
+
         ByteArrayOutputStream bo = getSubmitPduHead(
                 scAddress, destinationAddress, mtiByte,
                 statusReportRequested, ret);
@@ -338,7 +423,11 @@
             bo.write(0x08);
         }
 
-        // (no TP-Validity-Period)
+        if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) {
+            // ( TP-Validity-Period - relative format)
+            bo.write(relativeValidityPeriod);
+        }
+
         bo.write(userData, 0, userData.length);
         ret.encodedMessage = bo.toByteArray();
         return ret;
@@ -388,6 +477,24 @@
     }
 
     /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @param destinationAddress the address of the destination for the message
+     * @param statusReportRequested staus report of the message Requested
+     * @param validityPeriod Validity Period of the message in Minutes.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested, int validityPeriod) {
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+                null, ENCODING_UNKNOWN, 0, 0, validityPeriod);
+    }
+
+    /**
      * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
      *
      * @param scAddress Service Centre address. null == use default
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index f837b56..408dca5 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -50,7 +50,6 @@
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.telephony.ims.ImsServiceProxy;
 import android.telephony.ims.feature.ImsFeature;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -69,6 +68,7 @@
 import com.android.ims.ImsMultiEndpoint;
 import com.android.ims.ImsReasonInfo;
 import com.android.ims.ImsServiceClass;
+import com.android.ims.ImsServiceProxy;
 import com.android.ims.ImsSuppServiceNotification;
 import com.android.ims.ImsUtInterface;
 import com.android.ims.internal.IImsVideoCallProvider;
@@ -972,7 +972,7 @@
             return false;
         }
 
-        return mImsManager.isServiceAvailable();
+        return mImsManager.isServiceReady();
     }
 
     /**
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index 60a50b9..cea8b21 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -393,14 +393,6 @@
     }
 
     @Override
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-    }
-
-    @Override
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-    }
-
-    @Override
     public void setBandMode (int bandMode, Message response) {
     }
 
diff --git a/com/android/internal/telephony/sip/SipCommandInterface.java b/com/android/internal/telephony/sip/SipCommandInterface.java
index fe1d7c5..1264053 100644
--- a/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -394,14 +394,6 @@
     }
 
     @Override
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-    }
-
-    @Override
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-    }
-
-    @Override
     public void setBandMode (int bandMode, Message response) {
     }
 
diff --git a/com/android/internal/telephony/test/SimulatedCommands.java b/com/android/internal/telephony/test/SimulatedCommands.java
index 0de2bec..7553b61 100644
--- a/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/com/android/internal/telephony/test/SimulatedCommands.java
@@ -1446,15 +1446,6 @@
     }
 
     @Override
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-        // Just echo back data
-        if (response != null) {
-            AsyncResult.forMessage(response).result = data;
-            response.sendToTarget();
-        }
-    }
-
-    @Override
     public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
                                                 Message response) {
         // Just echo back data
@@ -1464,15 +1455,6 @@
         }
     }
 
-    @Override
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-        // Just echo back data
-        if (response != null) {
-            AsyncResult.forMessage(response).result = strings;
-            response.sendToTarget();
-        }
-    }
-
     //***** SimulatedRadioControl
 
 
diff --git a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index d746259..b03e70b 100644
--- a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -1046,26 +1046,6 @@
     }
 
     @Override
-    public void invokeOemRilRequestRaw(byte[] data, Message response) {
-
-    }
-
-    @Override
-    public void invokeOemRilRequestStrings(String[] strings, Message response) {
-
-    }
-
-    @Override
-    public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
-
-    }
-
-    @Override
-    public void unSetOnUnsolOemHookRaw(Handler h) {
-
-    }
-
-    @Override
     public void sendTerminalResponse(String contents, Message response) {
 
     }
diff --git a/com/android/internal/telephony/uicc/CarrierTestOverride.java b/com/android/internal/telephony/uicc/CarrierTestOverride.java
index 18d2937..755b79f 100644
--- a/com/android/internal/telephony/uicc/CarrierTestOverride.java
+++ b/com/android/internal/telephony/uicc/CarrierTestOverride.java
@@ -47,6 +47,7 @@
        <carrierTestOverride key="gid2" value="ffffffffffffffff"/>
        <carrierTestOverride key="imsi" value="310010123456789"/>
        <carrierTestOverride key="spn" value="Verizon"/>
+       <carrierTestOverride key="pnn" value="Verizon network"/>
        </carrierTestOverrides>
      */
     static final String DATA_CARRIER_TEST_OVERRIDE_PATH =
@@ -60,6 +61,7 @@
     static final String CARRIER_TEST_XML_ITEM_KEY_STRING_GID2 = "gid2";
     static final String CARRIER_TEST_XML_ITEM_KEY_STRING_IMSI = "imsi";
     static final String CARRIER_TEST_XML_ITEM_KEY_STRING_SPN = "spn";
+    static final String CARRIER_TEST_XML_ITEM_KEY_STRING_PNN = "pnn";
 
     private HashMap<String, String> mCarrierTestParamMap;
 
@@ -118,6 +120,17 @@
         }
     }
 
+    String getFakePnnHomeName() {
+        try {
+            String pnn = mCarrierTestParamMap.get(CARRIER_TEST_XML_ITEM_KEY_STRING_PNN);
+            Rlog.d(LOG_TAG, "reading pnn from CarrierTestConfig file: " + pnn);
+            return pnn;
+        } catch (NullPointerException e) {
+            Rlog.w(LOG_TAG, "No pnn in CarrierTestConfig file ");
+            return null;
+        }
+    }
+
     private void loadCarrierTestOverrides() {
 
         FileReader carrierTestConfigReader;
diff --git a/com/android/internal/telephony/uicc/IccRecords.java b/com/android/internal/telephony/uicc/IccRecords.java
index af26f5c..4e4158a 100644
--- a/com/android/internal/telephony/uicc/IccRecords.java
+++ b/com/android/internal/telephony/uicc/IccRecords.java
@@ -23,8 +23,10 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
@@ -57,6 +59,7 @@
     protected RegistrantList mRecordsEventsRegistrants = new RegistrantList();
     protected RegistrantList mNewSmsRegistrants = new RegistrantList();
     protected RegistrantList mNetworkSelectionModeAutomaticRegistrants = new RegistrantList();
+    protected RegistrantList mSpnUpdatedRegistrants = new RegistrantList();
 
     protected int mRecordsToLoad;  // number of pending load requests
 
@@ -92,6 +95,9 @@
     protected String mGid2;
     protected String mFakeGid2;
 
+    protected String mPnnHomeName;
+    protected String mFakePnnHomeName;
+
     protected String mPrefLang;
 
     protected PlmnActRecord[] mHplmnActRecords;
@@ -146,12 +152,6 @@
                 + " mCi=" + mCi
                 + " mFh=" + mFh
                 + " mParentApp=" + mParentApp
-                + " recordsLoadedRegistrants=" + mRecordsLoadedRegistrants
-                + " mImsiReadyRegistrants=" + mImsiReadyRegistrants
-                + " mRecordsEventsRegistrants=" + mRecordsEventsRegistrants
-                + " mNewSmsRegistrants=" + mNewSmsRegistrants
-                + " mNetworkSelectionModeAutomaticRegistrants="
-                        + mNetworkSelectionModeAutomaticRegistrants
                 + " recordsToLoad=" + mRecordsToLoad
                 + " adnCache=" + mAdnCache
                 + " recordsRequested=" + mRecordsRequested
@@ -164,13 +164,11 @@
                 + " isVoiceMailFixed=" + mIsVoiceMailFixed
                 + " mImsi=" + ((mImsi != null) ?
                 mImsi.substring(0, 6) + Rlog.pii(VDBG, mImsi.substring(6)) : "null")
-                + (mCarrierTestOverride.isInTestMode()
-                ? (" mFakeImsi=" + ((mFakeImsi != null) ? mFakeImsi : "null")) : "")
+                + (mCarrierTestOverride.isInTestMode() ? " mFakeImsi=" + mFakeImsi : "")
                 + " mncLength=" + mMncLength
                 + " mailboxIndex=" + mMailboxIndex
                 + " spn=" + mSpn
-                + (mCarrierTestOverride.isInTestMode()
-                ? (" mFakeSpn=" + ((mFakeSpn != null) ? mFakeSpn : "null")) : "");
+                + (mCarrierTestOverride.isInTestMode() ? " mFakeSpn=" + mFakeSpn : "");
 
     }
 
@@ -213,6 +211,9 @@
 
             mFakeSpn = mCarrierTestOverride.getFakeSpn();
             log("load mFakeSpn: " + mFakeSpn);
+
+            mFakePnnHomeName = mCarrierTestOverride.getFakePnnHomeName();
+            log("load mFakePnnHomeName: " + mFakePnnHomeName);
         }
     }
 
@@ -314,6 +315,22 @@
         mImsiReadyRegistrants.remove(h);
     }
 
+    public void registerForSpnUpdate(Handler h, int what, Object obj) {
+        if (mDestroyed.get()) {
+            return;
+        }
+
+        Registrant r = new Registrant(h, what, obj);
+        mSpnUpdatedRegistrants.add(r);
+
+        if (!TextUtils.isEmpty(mSpn)) {
+            r.notifyRegistrant(new AsyncResult(null, null, null));
+        }
+    }
+    public void unregisterForSpnUpdate(Handler h) {
+        mSpnUpdatedRegistrants.remove(h);
+    }
+
     public void registerForRecordsEvents(Handler h, int what, Object obj) {
         Registrant r = new Registrant (h, what, obj);
         mRecordsEventsRegistrants.add(r);
@@ -406,6 +423,18 @@
         }
     }
 
+    /**
+     * Get the PLMN network name on a SIM.
+     * @return null if SIM is not yet ready
+     */
+    public String getPnnHomeName() {
+        if (mCarrierTestOverride.isInTestMode() && mFakePnnHomeName != null) {
+            return mFakePnnHomeName;
+        } else {
+            return mPnnHomeName;
+        }
+    }
+
     public void setMsisdnNumber(String alphaTag, String number,
             Message onComplete) {
         loge("setMsisdn() should not be invoked on base IccRecords");
@@ -457,7 +486,10 @@
     }
 
     protected void setServiceProviderName(String spn) {
-        mSpn = spn;
+        if (!TextUtils.equals(mSpn, spn)) {
+            mSpnUpdatedRegistrants.notifyRegistrants();
+            mSpn = spn;
+        }
     }
 
     /**
@@ -635,13 +667,19 @@
 
     /**
      * Returns the SpnDisplayRule based on settings on the SIM and the
-     * specified plmn (currently-registered PLMN).  See TS 22.101 Annex A
-     * and TS 51.011 10.3.11 for details.
+     * current service state. See TS 22.101 Annex A and TS 51.011 10.3.11
+     * for details.
      *
      * If the SPN is not found on the SIM, the rule is always PLMN_ONLY.
      * Generally used for GSM/UMTS and the like SIMs.
+     *
+     * @param serviceState Service state
+     * @return the display rule
+     *
+     * @see #SPN_RULE_SHOW_SPN
+     * @see #SPN_RULE_SHOW_PLMN
      */
-    public abstract int getDisplayRule(String plmn);
+    public abstract int getDisplayRule(ServiceState serviceState);
 
     /**
      * Return true if "Restriction of menu options for manual PLMN selection"
@@ -821,13 +859,13 @@
         pw.println(" mImsi=" + ((mImsi != null) ?
                 mImsi.substring(0, 6) + Rlog.pii(VDBG, mImsi.substring(6)) : "null"));
         if (mCarrierTestOverride.isInTestMode()) {
-            pw.println(" mFakeImsi=" + ((mFakeImsi != null) ? mFakeImsi : "null"));
+            pw.println(" mFakeImsi=" + mFakeImsi);
         }
         pw.println(" mMncLength=" + mMncLength);
         pw.println(" mMailboxIndex=" + mMailboxIndex);
         pw.println(" mSpn=" + mSpn);
         if (mCarrierTestOverride.isInTestMode()) {
-            pw.println(" mFakeSpn=" + ((mFakeSpn != null) ? mFakeSpn : "null"));
+            pw.println(" mFakeSpn=" + mFakeSpn);
         }
         pw.flush();
     }
diff --git a/com/android/internal/telephony/uicc/IccUtils.java b/com/android/internal/telephony/uicc/IccUtils.java
index 62d570c..99a82ad 100644
--- a/com/android/internal/telephony/uicc/IccUtils.java
+++ b/com/android/internal/telephony/uicc/IccUtils.java
@@ -567,4 +567,12 @@
         } while (valueIndex < endIndex);
         return result;
     }
+
+    public static String getDecimalSubstring(String iccId) {
+        int position;
+        for (position = 0; position < iccId.length(); position ++) {
+            if (!Character.isDigit(iccId.charAt(position))) break;
+        }
+        return iccId.substring( 0, position );
+    }
 }
diff --git a/com/android/internal/telephony/uicc/IsimUiccRecords.java b/com/android/internal/telephony/uicc/IsimUiccRecords.java
index 194d259..3d1fafb 100644
--- a/com/android/internal/telephony/uicc/IsimUiccRecords.java
+++ b/com/android/internal/telephony/uicc/IsimUiccRecords.java
@@ -27,6 +27,7 @@
 import android.os.AsyncResult;
 import android.os.Message;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.CommandsInterface;
@@ -457,7 +458,7 @@
     }
 
     @Override
-    public int getDisplayRule(String plmn) {
+    public int getDisplayRule(ServiceState serviceState) {
         // Not applicable to Isim
         return 0;
     }
diff --git a/com/android/internal/telephony/uicc/RuimRecords.java b/com/android/internal/telephony/uicc/RuimRecords.java
index b303ca8..5cc343e 100644
--- a/com/android/internal/telephony/uicc/RuimRecords.java
+++ b/com/android/internal/telephony/uicc/RuimRecords.java
@@ -24,6 +24,7 @@
 import android.os.Message;
 import android.os.SystemProperties;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
@@ -868,7 +869,7 @@
      * No Display rule for RUIMs yet.
      */
     @Override
-    public int getDisplayRule(String plmn) {
+    public int getDisplayRule(ServiceState serviceState) {
         // TODO together with spn
         return 0;
     }
diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java
index 8aed931..8594f80 100644
--- a/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/com/android/internal/telephony/uicc/SIMRecords.java
@@ -23,9 +23,11 @@
 import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -91,8 +93,6 @@
     // Numeric network codes listed in TS 51.011 EF[SPDI]
     ArrayList<String> mSpdiNetworks = null;
 
-    String mPnnHomeName = null;
-
     UsimServiceTable mUsimServiceTable;
 
     @Override
@@ -484,7 +484,7 @@
         }
     }
 
-    // Validate data is !null.
+    // Validate data is not null and not empty.
     private boolean validEfCfis(byte[] data) {
         if (data != null) {
             if (data[0] < 1 || data[0] > 4) {
@@ -492,7 +492,12 @@
                 // 1 and 4 according to ETSI TS 131 102 v11.3.0 section 4.2.64.
                 logw("MSP byte: " + data[0] + " is not between 1 and 4", null);
             }
-            return true;
+            // empty EF_CFIS should be considered as call forward disabled
+            for (byte b : data) {
+                if (b != (byte) 0xFF) {
+                    return true;
+                }
+            }
         }
         return false;
     }
@@ -1068,6 +1073,7 @@
                         if (tlv.getTag() == TAG_FULL_NETWORK_NAME) {
                             mPnnHomeName = IccUtils.networkNameToString(
                                     tlv.getData(), 0, tlv.getData().length);
+                            log("PNN: " + mPnnHomeName);
                             break;
                         }
                     }
@@ -1840,14 +1846,20 @@
 
     /**
      * Returns the SpnDisplayRule based on settings on the SIM and the
-     * specified plmn (currently-registered PLMN).  See TS 22.101 Annex A
-     * and TS 51.011 10.3.11 for details.
+     * current service state. See TS 22.101 Annex A and TS 51.011 10.3.11
+     * for details.
      *
      * If the SPN is not found on the SIM or is empty, the rule is
      * always PLMN_ONLY.
+     *
+     * @param serviceState Service state
+     * @return the display rule
+     *
+     * @see #SPN_RULE_SHOW_SPN
+     * @see #SPN_RULE_SHOW_PLMN
      */
     @Override
-    public int getDisplayRule(String plmn) {
+    public int getDisplayRule(ServiceState serviceState) {
         int rule;
 
         if (mParentApp != null && mParentApp.getUiccCard() != null &&
@@ -1857,7 +1869,8 @@
         } else if (TextUtils.isEmpty(getServiceProviderName()) || mSpnDisplayCondition == -1) {
             // No EF_SPN content was found on the SIM, or not yet loaded.  Just show ONS.
             rule = SPN_RULE_SHOW_PLMN;
-        } else if (isOnMatchingPlmn(plmn)) {
+        } else if (useRoamingFromServiceState() ? !serviceState.getRoaming()
+                : isOnMatchingPlmn(serviceState.getOperatorNumeric())) {
             rule = SPN_RULE_SHOW_SPN;
             if ((mSpnDisplayCondition & 0x01) == 0x01) {
                 // ONS required when registered to HPLMN or PLMN in EF_SPDI
@@ -1873,6 +1886,21 @@
         return rule;
     }
 
+    private boolean useRoamingFromServiceState() {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle b = configManager.getConfigForSubId(
+                    SubscriptionController.getInstance().getSubIdUsingPhoneId(
+                    mParentApp.getPhoneId()));
+            if (b != null && b.getBoolean(CarrierConfigManager
+                    .KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Checks if plmn is HPLMN or on the spdiNetworks list.
      */
@@ -2210,11 +2238,15 @@
         pw.println(" mUsimServiceTable=" + mUsimServiceTable);
         pw.println(" mGid1=" + mGid1);
         if (mCarrierTestOverride.isInTestMode()) {
-            pw.println(" mFakeGid1=" + ((mFakeGid1 != null) ? mFakeGid1 : "null"));
+            pw.println(" mFakeGid1=" + mFakeGid1);
         }
         pw.println(" mGid2=" + mGid2);
         if (mCarrierTestOverride.isInTestMode()) {
-            pw.println(" mFakeGid2=" + ((mFakeGid2 != null) ? mFakeGid2 : "null"));
+            pw.println(" mFakeGid2=" + mFakeGid2);
+        }
+        pw.println(" mPnnHomeName=" + mPnnHomeName);
+        if (mCarrierTestOverride.isInTestMode()) {
+            pw.println(" mFakePnnHomeName=" + mFakePnnHomeName);
         }
         pw.println(" mPlmnActRecords[]=" + Arrays.toString(mPlmnActRecords));
         pw.println(" mOplmnActRecords[]=" + Arrays.toString(mOplmnActRecords));
diff --git a/com/android/internal/telephony/uicc/UiccCard.java b/com/android/internal/telephony/uicc/UiccCard.java
index baad60b..3a71ad3 100644
--- a/com/android/internal/telephony/uicc/UiccCard.java
+++ b/com/android/internal/telephony/uicc/UiccCard.java
@@ -33,11 +33,13 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.PowerManager;
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -47,6 +49,7 @@
 import com.android.internal.R;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.CommandsInterface.RadioState;
+import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.cat.CatService;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
@@ -744,7 +747,21 @@
             return null;
         }
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
-        return sp.getString(OPERATOR_BRAND_OVERRIDE_PREFIX + iccId, null);
+        String brandName = sp.getString(OPERATOR_BRAND_OVERRIDE_PREFIX + iccId, null);
+        if (brandName == null) {
+            // Check if  CarrierConfig sets carrier name
+            CarrierConfigManager manager = (CarrierConfigManager)
+                    mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+            int subId = SubscriptionController.getInstance().getSubIdUsingPhoneId(mPhoneId);
+            if (manager != null) {
+                PersistableBundle bundle = manager.getConfigForSubId(subId);
+                if (bundle != null && bundle.getBoolean(
+                        CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)) {
+                    brandName = bundle.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
+                }
+            }
+        }
+        return brandName;
     }
 
     public String getIccId() {
diff --git a/com/android/internal/telephony/uicc/UiccProfile.java b/com/android/internal/telephony/uicc/UiccProfile.java
new file mode 100644
index 0000000..3e7d2d7
--- /dev/null
+++ b/com/android/internal/telephony/uicc/UiccProfile.java
@@ -0,0 +1,751 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.uicc;
+
+import android.app.AlertDialog;
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Registrant;
+import android.os.RegistrantList;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.telephony.Rlog;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.cat.CatService;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
+import com.android.internal.telephony.uicc.IccCardStatus.PinState;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * This class represents the carrier profiles in the {@link UiccCard}. Each profile contains
+ * multiple {@link UiccCardApplication}, one {@link UiccCarrierPrivilegeRules} and one
+ * {@link CatService}.
+ *
+ * Profile is related to {@link android.telephony.SubscriptionInfo} but those two concepts are
+ * different. {@link android.telephony.SubscriptionInfo} contains all the subscription information
+ * while Profile contains all the {@link UiccCardApplication} which will be used to fetch those
+ * subscription information from the {@link UiccCard}.
+ *
+ * {@hide}
+ */
+public class UiccProfile {
+    protected static final String LOG_TAG = "UiccProfile";
+    protected static final boolean DBG = true;
+
+    private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
+
+    private final Object mLock = new Object();
+    private PinState mUniversalPinState;
+    private int mGsmUmtsSubscriptionAppIndex;
+    private int mCdmaSubscriptionAppIndex;
+    private int mImsSubscriptionAppIndex;
+    private UiccCardApplication[] mUiccApplications =
+        new UiccCardApplication[IccCardStatus.CARD_MAX_APPS];
+    private Context mContext;
+    private CommandsInterface mCi;
+    private UiccCard mUiccCard; //parent
+    private CatService mCatService;
+    private UiccCarrierPrivilegeRules mCarrierPrivilegeRules;
+
+    private RegistrantList mCarrierPrivilegeRegistrants = new RegistrantList();
+
+    private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 15;
+    private static final int EVENT_CLOSE_LOGICAL_CHANNEL_DONE = 16;
+    private static final int EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE = 17;
+    private static final int EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE = 18;
+    private static final int EVENT_SIM_IO_DONE = 19;
+    private static final int EVENT_CARRIER_PRIVILEGES_LOADED = 20;
+
+    private static final LocalLog sLocalLog = new LocalLog(100);
+
+    private final int mPhoneId;
+
+    public UiccProfile(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId,
+            UiccCard uiccCard) {
+        if (DBG) log("Creating profile");
+        mUiccCard = uiccCard;
+        mPhoneId = phoneId;
+        update(c, ci, ics);
+    }
+
+    /**
+     * Dispose the UiccProfile.
+     */
+    public void dispose() {
+        synchronized (mLock) {
+            if (DBG) log("Disposing profile");
+            if (mCatService != null) mCatService.dispose();
+            for (UiccCardApplication app : mUiccApplications) {
+                if (app != null) {
+                    app.dispose();
+                }
+            }
+            mCatService = null;
+            mUiccApplications = null;
+            mCarrierPrivilegeRules = null;
+        }
+    }
+
+    /**
+     * Update the UiccProfile.
+     */
+    public void update(Context c, CommandsInterface ci, IccCardStatus ics) {
+        synchronized (mLock) {
+            mUniversalPinState = ics.mUniversalPinState;
+            mGsmUmtsSubscriptionAppIndex = ics.mGsmUmtsSubscriptionAppIndex;
+            mCdmaSubscriptionAppIndex = ics.mCdmaSubscriptionAppIndex;
+            mImsSubscriptionAppIndex = ics.mImsSubscriptionAppIndex;
+            mContext = c;
+            mCi = ci;
+
+            //update applications
+            if (DBG) log(ics.mApplications.length + " applications");
+            for (int i = 0; i < mUiccApplications.length; i++) {
+                if (mUiccApplications[i] == null) {
+                    //Create newly added Applications
+                    if (i < ics.mApplications.length) {
+                        mUiccApplications[i] = new UiccCardApplication(mUiccCard,
+                                ics.mApplications[i], mContext, mCi);
+                    }
+                } else if (i >= ics.mApplications.length) {
+                    //Delete removed applications
+                    mUiccApplications[i].dispose();
+                    mUiccApplications[i] = null;
+                } else {
+                    //Update the rest
+                    mUiccApplications[i].update(ics.mApplications[i], mContext, mCi);
+                }
+            }
+
+            createAndUpdateCatServiceLocked();
+
+            log("Before privilege rules: " + mCarrierPrivilegeRules);
+            if (mCarrierPrivilegeRules == null) {
+                mCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccCard,
+                        mHandler.obtainMessage(EVENT_CARRIER_PRIVILEGES_LOADED));
+            }
+
+            sanitizeApplicationIndexesLocked();
+        }
+    }
+
+    private void createAndUpdateCatServiceLocked() {
+        if (mUiccApplications.length > 0 && mUiccApplications[0] != null) {
+            // Initialize or Reinitialize CatService
+            if (mCatService == null) {
+                mCatService = CatService.getInstance(mCi, mContext, mUiccCard, mPhoneId);
+            } else {
+                mCatService.update(mCi, mContext, mUiccCard);
+            }
+        } else {
+            if (mCatService != null) {
+                mCatService.dispose();
+            }
+            mCatService = null;
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        if (DBG) log("UiccProfile finalized");
+    }
+
+    /**
+     * This function makes sure that application indexes are valid
+     * and resets invalid indexes. (This should never happen, but in case
+     * RIL misbehaves we need to manage situation gracefully)
+     */
+    private void sanitizeApplicationIndexesLocked() {
+        mGsmUmtsSubscriptionAppIndex =
+                checkIndexLocked(
+                        mGsmUmtsSubscriptionAppIndex, AppType.APPTYPE_SIM, AppType.APPTYPE_USIM);
+        mCdmaSubscriptionAppIndex =
+                checkIndexLocked(
+                        mCdmaSubscriptionAppIndex, AppType.APPTYPE_RUIM, AppType.APPTYPE_CSIM);
+        mImsSubscriptionAppIndex =
+                checkIndexLocked(mImsSubscriptionAppIndex, AppType.APPTYPE_ISIM, null);
+    }
+
+    private int checkIndexLocked(int index, AppType expectedAppType, AppType altExpectedAppType) {
+        if (mUiccApplications == null || index >= mUiccApplications.length) {
+            loge("App index " + index + " is invalid since there are no applications");
+            return -1;
+        }
+
+        if (index < 0) {
+            // This is normal. (i.e. no application of this type)
+            return -1;
+        }
+
+        if (mUiccApplications[index].getType() != expectedAppType
+                && mUiccApplications[index].getType() != altExpectedAppType) {
+            loge("App index " + index + " is invalid since it's not "
+                    + expectedAppType + " and not " + altExpectedAppType);
+            return -1;
+        }
+
+        // Seems to be valid
+        return index;
+    }
+
+    /**
+     * Registers the handler when carrier privilege rules are loaded.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForCarrierPrivilegeRulesLoaded(Handler h, int what, Object obj) {
+        synchronized (mLock) {
+            Registrant r = new Registrant(h, what, obj);
+
+            mCarrierPrivilegeRegistrants.add(r);
+
+            if (areCarrierPriviligeRulesLoaded()) {
+                r.notifyRegistrant();
+            }
+        }
+    }
+
+    /**
+     * Unregister for notifications when carrier privilege rules are loaded.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForCarrierPrivilegeRulesLoaded(Handler h) {
+        synchronized (mLock) {
+            mCarrierPrivilegeRegistrants.remove(h);
+        }
+    }
+
+    protected Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
+                case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
+                case EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE:
+                case EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE:
+                case EVENT_SIM_IO_DONE:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        loglocal("Exception: " + ar.exception);
+                        log("Error in SIM access with exception" + ar.exception);
+                    }
+                    AsyncResult.forMessage((Message) ar.userObj, ar.result, ar.exception);
+                    ((Message) ar.userObj).sendToTarget();
+                    break;
+                case EVENT_CARRIER_PRIVILEGES_LOADED:
+                    onCarrierPriviligesLoadedMessage();
+                    break;
+                default:
+                    loge("Unknown Event " + msg.what);
+            }
+        }
+    };
+
+    private boolean isPackageInstalled(String pkgName) {
+        PackageManager pm = mContext.getPackageManager();
+        try {
+            pm.getPackageInfo(pkgName, PackageManager.GET_ACTIVITIES);
+            if (DBG) log(pkgName + " is installed.");
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            if (DBG) log(pkgName + " is not installed.");
+            return false;
+        }
+    }
+
+    private class ClickListener implements DialogInterface.OnClickListener {
+        String mPkgName;
+        ClickListener(String pkgName) {
+            this.mPkgName = pkgName;
+        }
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            synchronized (mLock) {
+                if (which == DialogInterface.BUTTON_POSITIVE) {
+                    Intent market = new Intent(Intent.ACTION_VIEW);
+                    market.setData(Uri.parse("market://details?id=" + mPkgName));
+                    market.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    mContext.startActivity(market);
+                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+                    if (DBG) log("Not now clicked for carrier app dialog.");
+                }
+            }
+        }
+    }
+
+    private void promptInstallCarrierApp(String pkgName) {
+        DialogInterface.OnClickListener listener = new ClickListener(pkgName);
+
+        Resources r = Resources.getSystem();
+        String message = r.getString(R.string.carrier_app_dialog_message);
+        String buttonTxt = r.getString(R.string.carrier_app_dialog_button);
+        String notNowTxt = r.getString(R.string.carrier_app_dialog_not_now);
+
+        AlertDialog dialog = new AlertDialog.Builder(mContext)
+                .setMessage(message)
+                .setNegativeButton(notNowTxt, listener)
+                .setPositiveButton(buttonTxt, listener)
+                .create();
+        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        dialog.show();
+    }
+
+    private void onCarrierPriviligesLoadedMessage() {
+        UsageStatsManager usm = (UsageStatsManager) mContext.getSystemService(
+                Context.USAGE_STATS_SERVICE);
+        if (usm != null) {
+            usm.onCarrierPrivilegedAppsChanged();
+        }
+        synchronized (mLock) {
+            mCarrierPrivilegeRegistrants.notifyRegistrants();
+            String whitelistSetting = Settings.Global.getString(mContext.getContentResolver(),
+                    Settings.Global.CARRIER_APP_WHITELIST);
+            if (TextUtils.isEmpty(whitelistSetting)) {
+                return;
+            }
+            HashSet<String> carrierAppSet = new HashSet<String>(
+                    Arrays.asList(whitelistSetting.split("\\s*;\\s*")));
+            if (carrierAppSet.isEmpty()) {
+                return;
+            }
+
+            List<String> pkgNames = mCarrierPrivilegeRules.getPackageNames();
+            for (String pkgName : pkgNames) {
+                if (!TextUtils.isEmpty(pkgName) && carrierAppSet.contains(pkgName)
+                        && !isPackageInstalled(pkgName)) {
+                    promptInstallCarrierApp(pkgName);
+                }
+            }
+        }
+    }
+
+    /**
+     * Check whether the specified type of application exists in the profile.
+     *
+     * @param type UICC application type.
+     */
+    public boolean isApplicationOnIcc(IccCardApplicationStatus.AppType type) {
+        synchronized (mLock) {
+            for (int i = 0; i < mUiccApplications.length; i++) {
+                if (mUiccApplications[i] != null && mUiccApplications[i].getType() == type) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Return the universal pin state of the profile.
+     */
+    public PinState getUniversalPinState() {
+        synchronized (mLock) {
+            return mUniversalPinState;
+        }
+    }
+
+    /**
+     * Return the application of the specified family.
+     *
+     * @param family UICC application family.
+     * @return application corresponding to family or a null if no match found
+     */
+    public UiccCardApplication getApplication(int family) {
+        synchronized (mLock) {
+            int index = IccCardStatus.CARD_MAX_APPS;
+            switch (family) {
+                case UiccController.APP_FAM_3GPP:
+                    index = mGsmUmtsSubscriptionAppIndex;
+                    break;
+                case UiccController.APP_FAM_3GPP2:
+                    index = mCdmaSubscriptionAppIndex;
+                    break;
+                case UiccController.APP_FAM_IMS:
+                    index = mImsSubscriptionAppIndex;
+                    break;
+            }
+            if (index >= 0 && index < mUiccApplications.length) {
+                return mUiccApplications[index];
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Return the application with the index of the array.
+     *
+     * @param index Index of the application array.
+     * @return application corresponding to index or a null if no match found
+     */
+    public UiccCardApplication getApplicationIndex(int index) {
+        synchronized (mLock) {
+            if (index >= 0 && index < mUiccApplications.length) {
+                return mUiccApplications[index];
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Returns the SIM application of the specified type.
+     *
+     * @param type ICC application type
+     * (@see com.android.internal.telephony.PhoneConstants#APPTYPE_xxx)
+     * @return application corresponding to type or a null if no match found
+     */
+    public UiccCardApplication getApplicationByType(int type) {
+        synchronized (mLock) {
+            for (int i = 0; i < mUiccApplications.length; i++) {
+                if (mUiccApplications[i] != null
+                        && mUiccApplications[i].getType().ordinal() == type) {
+                    return mUiccApplications[i];
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Resets the application with the input AID. Returns true if any changes were made.
+     *
+     * A null aid implies a card level reset - all applications must be reset.
+     */
+    public boolean resetAppWithAid(String aid) {
+        synchronized (mLock) {
+            boolean changed = false;
+            for (int i = 0; i < mUiccApplications.length; i++) {
+                if (mUiccApplications[i] != null
+                        && (TextUtils.isEmpty(aid) || aid.equals(mUiccApplications[i].getAid()))) {
+                    // Delete removed applications
+                    mUiccApplications[i].dispose();
+                    mUiccApplications[i] = null;
+                    changed = true;
+                }
+            }
+            if (TextUtils.isEmpty(aid)) {
+                if (mCarrierPrivilegeRules != null) {
+                    mCarrierPrivilegeRules = null;
+                    changed = true;
+                }
+                if (mCatService != null) {
+                    mCatService.dispose();
+                    mCatService = null;
+                    changed = true;
+                }
+            }
+            return changed;
+        }
+    }
+
+    /**
+     * Exposes {@link CommandsInterface#iccOpenLogicalChannel}
+     */
+    public void iccOpenLogicalChannel(String aid, int p2, Message response) {
+        loglocal("Open Logical Channel: " + aid + " , " + p2 + " by pid:" + Binder.getCallingPid()
+                + " uid:" + Binder.getCallingUid());
+        mCi.iccOpenLogicalChannel(aid, p2,
+                mHandler.obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, response));
+    }
+
+    /**
+     * Exposes {@link CommandsInterface#iccCloseLogicalChannel}
+     */
+    public void iccCloseLogicalChannel(int channel, Message response) {
+        loglocal("Close Logical Channel: " + channel);
+        mCi.iccCloseLogicalChannel(channel,
+                mHandler.obtainMessage(EVENT_CLOSE_LOGICAL_CHANNEL_DONE, response));
+    }
+
+    /**
+     * Exposes {@link CommandsInterface#iccTransmitApduLogicalChannel}
+     */
+    public void iccTransmitApduLogicalChannel(int channel, int cla, int command,
+            int p1, int p2, int p3, String data, Message response) {
+        mCi.iccTransmitApduLogicalChannel(channel, cla, command, p1, p2, p3,
+                data, mHandler.obtainMessage(EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE, response));
+    }
+
+    /**
+     * Exposes {@link CommandsInterface#iccTransmitApduBasicChannel}
+     */
+    public void iccTransmitApduBasicChannel(int cla, int command,
+            int p1, int p2, int p3, String data, Message response) {
+        mCi.iccTransmitApduBasicChannel(cla, command, p1, p2, p3,
+                data, mHandler.obtainMessage(EVENT_TRANSMIT_APDU_BASIC_CHANNEL_DONE, response));
+    }
+
+    /**
+     * Exposes {@link CommandsInterface#iccIO}
+     */
+    public void iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
+            String pathID, Message response) {
+        mCi.iccIO(command, fileID, pathID, p1, p2, p3, null, null,
+                mHandler.obtainMessage(EVENT_SIM_IO_DONE, response));
+    }
+
+    /**
+     * Exposes {@link CommandsInterface#sendEnvelopeWithStatus}
+     */
+    public void sendEnvelopeWithStatus(String contents, Message response) {
+        mCi.sendEnvelopeWithStatus(contents, response);
+    }
+
+    /**
+     * Returns number of applications on this card
+     */
+    public int getNumApplications() {
+        int count = 0;
+        for (UiccCardApplication a : mUiccApplications) {
+            if (a != null) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Returns the id of the phone which is associated with this profile.
+     */
+    public int getPhoneId() {
+        return mPhoneId;
+    }
+
+    /**
+     * Returns true iff carrier privileges rules are null (dont need to be loaded) or loaded.
+     */
+    public boolean areCarrierPriviligeRulesLoaded() {
+        UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+        return carrierPrivilegeRules == null
+                || carrierPrivilegeRules.areCarrierPriviligeRulesLoaded();
+    }
+
+    /**
+     * Returns true if there are some carrier privilege rules loaded and specified.
+     */
+    public boolean hasCarrierPrivilegeRules() {
+        UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+        return carrierPrivilegeRules != null && carrierPrivilegeRules.hasCarrierPrivilegeRules();
+    }
+
+    /**
+     * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPrivilegeStatus}.
+     */
+    public int getCarrierPrivilegeStatus(Signature signature, String packageName) {
+        UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+        return carrierPrivilegeRules == null
+                ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED :
+                carrierPrivilegeRules.getCarrierPrivilegeStatus(signature, packageName);
+    }
+
+    /**
+     * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPrivilegeStatus}.
+     */
+    public int getCarrierPrivilegeStatus(PackageManager packageManager, String packageName) {
+        UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+        return carrierPrivilegeRules == null
+                ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED :
+                carrierPrivilegeRules.getCarrierPrivilegeStatus(packageManager, packageName);
+    }
+
+    /**
+     * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPrivilegeStatus}.
+     */
+    public int getCarrierPrivilegeStatus(PackageInfo packageInfo) {
+        UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+        return carrierPrivilegeRules == null
+                ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED :
+                carrierPrivilegeRules.getCarrierPrivilegeStatus(packageInfo);
+    }
+
+    /**
+     * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPrivilegeStatusForCurrentTransaction}.
+     */
+    public int getCarrierPrivilegeStatusForCurrentTransaction(PackageManager packageManager) {
+        UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+        return carrierPrivilegeRules == null
+                ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED :
+                carrierPrivilegeRules.getCarrierPrivilegeStatusForCurrentTransaction(
+                        packageManager);
+    }
+
+    /**
+     * Exposes {@link UiccCarrierPrivilegeRules#getCarrierPackageNamesForIntent}.
+     */
+    public List<String> getCarrierPackageNamesForIntent(
+            PackageManager packageManager, Intent intent) {
+        UiccCarrierPrivilegeRules carrierPrivilegeRules = getCarrierPrivilegeRules();
+        return carrierPrivilegeRules == null ? null :
+                carrierPrivilegeRules.getCarrierPackageNamesForIntent(
+                        packageManager, intent);
+    }
+
+    /** Returns a reference to the current {@link UiccCarrierPrivilegeRules}. */
+    private UiccCarrierPrivilegeRules getCarrierPrivilegeRules() {
+        synchronized (mLock) {
+            return mCarrierPrivilegeRules;
+        }
+    }
+
+    /**
+     * Sets the overridden operator brand.
+     */
+    public boolean setOperatorBrandOverride(String brand) {
+        log("setOperatorBrandOverride: " + brand);
+        log("current iccId: " + getIccId());
+
+        String iccId = getIccId();
+        if (TextUtils.isEmpty(iccId)) {
+            return false;
+        }
+
+        SharedPreferences.Editor spEditor =
+                PreferenceManager.getDefaultSharedPreferences(mContext).edit();
+        String key = OPERATOR_BRAND_OVERRIDE_PREFIX + iccId;
+        if (brand == null) {
+            spEditor.remove(key).commit();
+        } else {
+            spEditor.putString(key, brand).commit();
+        }
+        return true;
+    }
+
+    /**
+     * Returns the overridden operator brand.
+     */
+    public String getOperatorBrandOverride() {
+        String iccId = getIccId();
+        if (TextUtils.isEmpty(iccId)) {
+            return null;
+        }
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+        return sp.getString(OPERATOR_BRAND_OVERRIDE_PREFIX + iccId, null);
+    }
+
+    /**
+     * Returns the iccid of the profile.
+     */
+    public String getIccId() {
+        // ICCID should be same across all the apps.
+        for (UiccCardApplication app : mUiccApplications) {
+            if (app != null) {
+                IccRecords ir = app.getIccRecords();
+                if (ir != null && ir.getIccId() != null) {
+                    return ir.getIccId();
+                }
+            }
+        }
+        return null;
+    }
+
+    private void log(String msg) {
+        Rlog.d(LOG_TAG, msg);
+    }
+
+    private void loge(String msg) {
+        Rlog.e(LOG_TAG, msg);
+    }
+
+    private void loglocal(String msg) {
+        if (DBG) sLocalLog.log(msg);
+    }
+
+    /**
+     * Dump
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("UiccProfile:");
+        pw.println(" mCi=" + mCi);
+        pw.println(" mCatService=" + mCatService);
+        for (int i = 0; i < mCarrierPrivilegeRegistrants.size(); i++) {
+            pw.println("  mCarrierPrivilegeRegistrants[" + i + "]="
+                    + ((Registrant) mCarrierPrivilegeRegistrants.get(i)).getHandler());
+        }
+        pw.println(" mUniversalPinState=" + mUniversalPinState);
+        pw.println(" mGsmUmtsSubscriptionAppIndex=" + mGsmUmtsSubscriptionAppIndex);
+        pw.println(" mCdmaSubscriptionAppIndex=" + mCdmaSubscriptionAppIndex);
+        pw.println(" mImsSubscriptionAppIndex=" + mImsSubscriptionAppIndex);
+        pw.println(" mUiccApplications: length=" + mUiccApplications.length);
+        for (int i = 0; i < mUiccApplications.length; i++) {
+            if (mUiccApplications[i] == null) {
+                pw.println("  mUiccApplications[" + i + "]=" + null);
+            } else {
+                pw.println("  mUiccApplications[" + i + "]="
+                        + mUiccApplications[i].getType() + " " + mUiccApplications[i]);
+            }
+        }
+        pw.println();
+        // Print details of all applications
+        for (UiccCardApplication app : mUiccApplications) {
+            if (app != null) {
+                app.dump(fd, pw, args);
+                pw.println();
+            }
+        }
+        // Print details of all IccRecords
+        for (UiccCardApplication app : mUiccApplications) {
+            if (app != null) {
+                IccRecords ir = app.getIccRecords();
+                if (ir != null) {
+                    ir.dump(fd, pw, args);
+                    pw.println();
+                }
+            }
+        }
+        // Print UiccCarrierPrivilegeRules and registrants.
+        if (mCarrierPrivilegeRules == null) {
+            pw.println(" mCarrierPrivilegeRules: null");
+        } else {
+            pw.println(" mCarrierPrivilegeRules: " + mCarrierPrivilegeRules);
+            mCarrierPrivilegeRules.dump(fd, pw, args);
+        }
+        pw.println(" mCarrierPrivilegeRegistrants: size=" + mCarrierPrivilegeRegistrants.size());
+        for (int i = 0; i < mCarrierPrivilegeRegistrants.size(); i++) {
+            pw.println("  mCarrierPrivilegeRegistrants[" + i + "]="
+                    + ((Registrant) mCarrierPrivilegeRegistrants.get(i)).getHandler());
+        }
+        pw.flush();
+        pw.println("sLocalLog:");
+        sLocalLog.dump(fd, pw, args);
+        pw.flush();
+    }
+}
diff --git a/com/android/internal/util/Predicate.java b/com/android/internal/util/Predicate.java
index 1b5eaff..e87f489 100644
--- a/com/android/internal/util/Predicate.java
+++ b/com/android/internal/util/Predicate.java
@@ -27,6 +27,7 @@
  * strongly encouraged to state this fact clearly in their API documentation.
  *
  * @deprecated Use {@code java.util.function.Predicate} instead.
+ *             This must not be used outside frameworks/base/test-runner.
  */
 @Deprecated
 public interface Predicate<T> {
diff --git a/com/android/internal/util/RingBuffer.java b/com/android/internal/util/RingBuffer.java
index ad84353..c22be2c 100644
--- a/com/android/internal/util/RingBuffer.java
+++ b/com/android/internal/util/RingBuffer.java
@@ -45,6 +45,17 @@
         return (int) Math.min(mBuffer.length, (long) mCursor);
     }
 
+    public boolean isEmpty() {
+        return size() == 0;
+    }
+
+    public void clear() {
+        for (int i = 0; i < size(); ++i) {
+            mBuffer[i] = null;
+        }
+        mCursor = 0;
+    }
+
     public void append(T t) {
         mBuffer[indexOf(mCursor++)] = t;
     }
diff --git a/com/android/internal/util/StateMachine.java b/com/android/internal/util/StateMachine.java
index 8d9630f..e5ad1f4 100644
--- a/com/android/internal/util/StateMachine.java
+++ b/com/android/internal/util/StateMachine.java
@@ -804,7 +804,7 @@
 
                 /** State that processed the message */
                 State msgProcessedState = null;
-                if (mIsConstructionCompleted) {
+                if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
                     /** Normal path */
                     msgProcessedState = processMsg(msg);
                 } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
diff --git a/com/android/internal/widget/ImageFloatingTextView.java b/com/android/internal/widget/ImageFloatingTextView.java
index 7870333..09f7282 100644
--- a/com/android/internal/widget/ImageFloatingTextView.java
+++ b/com/android/internal/widget/ImageFloatingTextView.java
@@ -176,8 +176,4 @@
         }
         return false;
     }
-
-    public int getLayoutHeight() {
-        return getLayout().getHeight();
-    }
 }
diff --git a/com/android/internal/widget/MessagingGroup.java b/com/android/internal/widget/MessagingGroup.java
new file mode 100644
index 0000000..792f921
--- /dev/null
+++ b/com/android/internal/widget/MessagingGroup.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
+    private static Pools.SimplePool<MessagingGroup> sInstancePool
+            = new Pools.SynchronizedPool<>(10);
+    private MessagingLinearLayout mMessageContainer;
+    private ImageFloatingTextView mSenderName;
+    private ImageView mAvatarView;
+    private String mAvatarSymbol = "";
+    private int mLayoutColor;
+    private CharSequence mAvatarName = "";
+    private Icon mAvatarIcon;
+    private ColorFilter mMessageBackgroundFilter;
+    private int mTextColor;
+    private List<MessagingMessage> mMessages;
+    private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>();
+    private boolean mFirstLayout;
+    private boolean mIsHidingAnimated;
+
+    public MessagingGroup(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mMessageContainer = findViewById(R.id.group_message_container);
+        mSenderName = findViewById(R.id.message_name);
+        mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+        mAvatarView = findViewById(R.id.message_icon);
+    }
+
+    public void setSender(CharSequence sender) {
+        if (sender == null) {
+            mAvatarView.setVisibility(GONE);
+            mSenderName.setVisibility(GONE);
+            setGravity(Gravity.END);
+            mMessageBackgroundFilter = new PorterDuffColorFilter(mLayoutColor,
+                    PorterDuff.Mode.SRC_ATOP);
+            mTextColor = NotificationColorUtil.isColorLight(mLayoutColor) ? getNormalTextColor()
+                    : Color.WHITE;
+        } else {
+            mSenderName.setText(sender);
+            mAvatarView.setVisibility(VISIBLE);
+            mSenderName.setVisibility(VISIBLE);
+            setGravity(Gravity.START);
+            mMessageBackgroundFilter = null;
+            mTextColor = getNormalTextColor();
+        }
+    }
+
+    private int getNormalTextColor() {
+        return mContext.getColor(R.color.notification_primary_text_color_light);
+    }
+
+    public void setAvatar(Icon icon) {
+        mAvatarIcon = icon;
+        mAvatarView.setImageIcon(icon);
+        mAvatarSymbol = "";
+        mLayoutColor = 0;
+        mAvatarName = "";
+    }
+
+    static MessagingGroup createGroup(MessagingLinearLayout layout) {;
+        MessagingGroup createdGroup = sInstancePool.acquire();
+        if (createdGroup == null) {
+            createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
+                    R.layout.notification_template_messaging_group, layout,
+                    false);
+            createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+        }
+        layout.addView(createdGroup);
+        return createdGroup;
+    }
+
+    public void removeMessage(MessagingMessage messagingMessage) {
+        mMessageContainer.removeView(messagingMessage);
+        Runnable recycleRunnable = () -> {
+            mMessageContainer.removeTransientView(messagingMessage);
+            messagingMessage.recycle();
+            if (mMessageContainer.getChildCount() == 0
+                    && mMessageContainer.getTransientViewCount() == 0) {
+                ViewParent parent = getParent();
+                if (parent instanceof ViewGroup) {
+                    ((ViewGroup) parent).removeView(MessagingGroup.this);
+                }
+                setAvatar(null);
+                mAvatarView.setAlpha(1.0f);
+                mAvatarView.setTranslationY(0.0f);
+                mSenderName.setAlpha(1.0f);
+                mSenderName.setTranslationY(0.0f);
+                sInstancePool.release(MessagingGroup.this);
+            }
+        };
+        if (isShown()) {
+            mMessageContainer.addTransientView(messagingMessage, 0);
+            performRemoveAnimation(messagingMessage, recycleRunnable);
+            if (mMessageContainer.getChildCount() == 0) {
+                removeGroupAnimated(null);
+            }
+        } else {
+            recycleRunnable.run();
+        }
+
+    }
+
+    private void removeGroupAnimated(Runnable endAction) {
+        MessagingPropertyAnimator.fadeOut(mAvatarView, null);
+        MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
+                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+        MessagingPropertyAnimator.fadeOut(mSenderName, null);
+        MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
+                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+        boolean endActionTriggered = false;
+        for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+            View child = mMessageContainer.getChildAt(i);
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+            final ViewGroup.LayoutParams lp = child.getLayoutParams();
+            if (lp instanceof MessagingLinearLayout.LayoutParams
+                    && ((MessagingLinearLayout.LayoutParams) lp).hide
+                    && !((MessagingLinearLayout.LayoutParams) lp).visibleBefore) {
+                continue;
+            }
+            Runnable childEndAction = endActionTriggered ? null : endAction;
+            performRemoveAnimation(child, childEndAction);
+            endActionTriggered = true;
+        }
+        if (!endActionTriggered && endAction != null) {
+            endAction.run();
+        }
+    }
+
+    public void performRemoveAnimation(View message,
+            Runnable recycleRunnable) {
+        MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
+        MessagingPropertyAnimator.startLocalTranslationTo(message,
+                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+    }
+
+    public CharSequence getSenderName() {
+        return mSenderName.getText();
+    }
+
+    public void setSenderVisible(boolean visible) {
+        mSenderName.setVisibility(visible ? VISIBLE : GONE);
+    }
+
+    public static void dropCache() {
+        sInstancePool = new Pools.SynchronizedPool<>(10);
+    }
+
+    @Override
+    public int getMeasuredType() {
+        boolean hasNormal = false;
+        for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+            View child = mMessageContainer.getChildAt(i);
+            if (child instanceof MessagingLinearLayout.MessagingChild) {
+                int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType();
+                if (type == MEASURED_TOO_SMALL) {
+                    if (hasNormal) {
+                        return MEASURED_SHORTENED;
+                    } else {
+                        return MEASURED_TOO_SMALL;
+                    }
+                } else if (type == MEASURED_SHORTENED) {
+                    return MEASURED_SHORTENED;
+                } else {
+                    hasNormal = true;
+                }
+            }
+        }
+        return MEASURED_NORMAL;
+    }
+
+    @Override
+    public int getConsumedLines() {
+        int result = 0;
+        for (int i = 0; i < mMessageContainer.getChildCount(); i++) {
+            View child = mMessageContainer.getChildAt(i);
+            if (child instanceof MessagingLinearLayout.MessagingChild) {
+                result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
+            }
+        }
+        // A group is usually taking up quite some space with the padding and the name, let's add 1
+        return result + 1;
+    }
+
+    @Override
+    public void setMaxDisplayedLines(int lines) {
+        mMessageContainer.setMaxDisplayedLines(lines);
+    }
+
+    @Override
+    public void hideAnimated() {
+        setIsHidingAnimated(true);
+        removeGroupAnimated(() -> setIsHidingAnimated(false));
+    }
+
+    @Override
+    public boolean isHidingAnimated() {
+        return mIsHidingAnimated;
+    }
+
+    private void setIsHidingAnimated(boolean isHiding) {
+        ViewParent parent = getParent();
+        mIsHidingAnimated = isHiding;
+        invalidate();
+        if (parent instanceof ViewGroup) {
+            ((ViewGroup) parent).invalidate();
+        }
+    }
+
+    public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
+            int layoutColor) {
+        if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
+                && layoutColor == mLayoutColor) {
+            return mAvatarIcon;
+        }
+        return null;
+    }
+
+    public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol,
+            int layoutColor) {
+        if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol)
+                || layoutColor != mLayoutColor) {
+            setAvatar(cachedIcon);
+            mAvatarSymbol = avatarSymbol;
+            mLayoutColor = layoutColor;
+            mAvatarName = avatarName;
+        }
+    }
+
+    public void setLayoutColor(int layoutColor) {
+        mLayoutColor = layoutColor;
+    }
+
+    public void setMessages(List<MessagingMessage> group) {
+        // Let's now make sure all children are added and in the correct order
+        for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
+            MessagingMessage message = group.get(messageIndex);
+            if (message.getGroup() != this) {
+                message.setMessagingGroup(this);
+                ViewParent parent = mMessageContainer.getParent();
+                if (parent instanceof ViewGroup) {
+                    ((ViewGroup) parent).removeView(message);
+                }
+                mMessageContainer.addView(message, messageIndex);
+                mAddedMessages.add(message);
+            }
+            if (messageIndex != mMessageContainer.indexOfChild(message)) {
+                mMessageContainer.removeView(message);
+                mMessageContainer.addView(message, messageIndex);
+            }
+            // Let's make sure the message color is correct
+            Drawable targetDrawable = message.getBackground();
+
+            if (targetDrawable != null) {
+                targetDrawable.mutate().setColorFilter(mMessageBackgroundFilter);
+            }
+            message.setTextColor(mTextColor);
+        }
+        mMessages = group;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (!mAddedMessages.isEmpty()) {
+            getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    for (MessagingMessage message : mAddedMessages) {
+                        if (!message.isShown()) {
+                            continue;
+                        }
+                        MessagingPropertyAnimator.fadeIn(message);
+                        if (!mFirstLayout) {
+                            MessagingPropertyAnimator.startLocalTranslationFrom(message,
+                                    message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
+                        }
+                    }
+                    mAddedMessages.clear();
+                    getViewTreeObserver().removeOnPreDrawListener(this);
+                    return true;
+                }
+            });
+        }
+        mFirstLayout = false;
+    }
+
+    /**
+     * Calculates the group compatibility between this and another group.
+     *
+     * @param otherGroup the other group to compare it with
+     *
+     * @return 0 if the groups are totally incompatible or 1 + the number of matching messages if
+     *         they match.
+     */
+    public int calculateGroupCompatibility(MessagingGroup otherGroup) {
+        if (TextUtils.equals(getSenderName(),otherGroup.getSenderName())) {
+            int result = 1;
+            for (int i = 0; i < mMessages.size() && i < otherGroup.mMessages.size(); i++) {
+                MessagingMessage ownMessage = mMessages.get(mMessages.size() - 1 - i);
+                MessagingMessage otherMessage = otherGroup.mMessages.get(
+                        otherGroup.mMessages.size() - 1 - i);
+                if (!ownMessage.sameAs(otherMessage)) {
+                    return result;
+                }
+                result++;
+            }
+            return result;
+        }
+        return 0;
+    }
+
+    public View getSender() {
+        return mSenderName;
+    }
+
+    public View getAvatar() {
+        return mAvatarView;
+    }
+
+    public MessagingLinearLayout getMessageContainer() {
+        return mMessageContainer;
+    }
+}
diff --git a/com/android/internal/widget/MessagingLayout.java b/com/android/internal/widget/MessagingLayout.java
new file mode 100644
index 0000000..2acdc01
--- /dev/null
+++ b/com/android/internal/widget/MessagingLayout.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
+ * messages and adapts the layout accordingly.
+ */
+@RemoteViews.RemoteView
+public class MessagingLayout extends FrameLayout {
+
+    private static final float COLOR_SHIFT_AMOUNT = 60;
+    private static final Consumer<MessagingMessage> REMOVE_MESSAGE
+            = MessagingMessage::removeMessage;
+    public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
+    public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
+            = new MessagingPropertyAnimator();
+    private List<MessagingMessage> mMessages = new ArrayList<>();
+    private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
+    private MessagingLinearLayout mMessagingLinearLayout;
+    private View mContractedMessage;
+    private boolean mShowHistoricMessages;
+    private ArrayList<MessagingGroup> mGroups = new ArrayList<>();
+    private TextView mTitleView;
+    private int mLayoutColor;
+    private int mAvatarSize;
+    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private Paint mTextPaint = new Paint();
+    private CharSequence mConversationTitle;
+    private Icon mLargeIcon;
+    private boolean mIsOneToOne;
+    private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
+
+    public MessagingLayout(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mMessagingLinearLayout = findViewById(R.id.notification_messaging);
+        mMessagingLinearLayout.setMessagingLayout(this);
+        // We still want to clip, but only on the top, since views can temporarily out of bounds
+        // during transitions.
+        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+        Rect rect = new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+        mMessagingLinearLayout.setClipBounds(rect);
+        mTitleView = findViewById(R.id.title);
+        mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+        mTextPaint.setAntiAlias(true);
+    }
+
+    @RemotableViewMethod
+    public void setLargeIcon(Icon icon) {
+        mLargeIcon = icon;
+    }
+
+    @RemotableViewMethod
+    public void setData(Bundle extras) {
+        Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+        List<Notification.MessagingStyle.Message> newMessages
+                = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
+        Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
+        List<Notification.MessagingStyle.Message> newHistoricMessages
+                = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
+        mConversationTitle = null;
+        TextView headerText = findViewById(R.id.header_text);
+        if (headerText != null) {
+            mConversationTitle = headerText.getText();
+        }
+        bind(newMessages, newHistoricMessages);
+    }
+
+    private void bind(List<Notification.MessagingStyle.Message> newMessages,
+            List<Notification.MessagingStyle.Message> newHistoricMessages) {
+
+        List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
+                true /* isHistoric */);
+        List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
+        addMessagesToGroups(historicMessages, messages);
+
+        // Let's remove the remaining messages
+        mMessages.forEach(REMOVE_MESSAGE);
+        mHistoricMessages.forEach(REMOVE_MESSAGE);
+
+        mMessages = messages;
+        mHistoricMessages = historicMessages;
+
+        updateContractedMessage();
+        updateHistoricMessageVisibility();
+        updateTitleAndNamesDisplay();
+    }
+
+    private void updateTitleAndNamesDisplay() {
+        ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
+        ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+        for (int i = 0; i < mGroups.size(); i++) {
+            MessagingGroup group = mGroups.get(i);
+            CharSequence senderName = group.getSenderName();
+            if (TextUtils.isEmpty(senderName)) {
+                continue;
+            }
+            boolean visible = !mIsOneToOne;
+            group.setSenderVisible(visible);
+            if ((visible || mLargeIcon == null) && !uniqueNames.containsKey(senderName)) {
+                char c = senderName.charAt(0);
+                if (uniqueCharacters.containsKey(c)) {
+                    // this character was already used, lets make it more unique. We first need to
+                    // resolve the existing character if it exists
+                    CharSequence existingName = uniqueCharacters.get(c);
+                    if (existingName != null) {
+                        uniqueNames.put(existingName, findNameSplit((String) existingName));
+                        uniqueCharacters.put(c, null);
+                    }
+                    uniqueNames.put(senderName, findNameSplit((String) senderName));
+                } else {
+                    uniqueNames.put(senderName, Character.toString(c));
+                    uniqueCharacters.put(c, senderName);
+                }
+            }
+        }
+
+        // Now that we have the correct symbols, let's look what we have cached
+        ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();
+        for (int i = 0; i < mGroups.size(); i++) {
+            // Let's now set the avatars
+            MessagingGroup group = mGroups.get(i);
+            CharSequence senderName = group.getSenderName();
+            if (TextUtils.isEmpty(senderName) || (mIsOneToOne && mLargeIcon != null)) {
+                continue;
+            }
+            String symbol = uniqueNames.get(senderName);
+            Icon cachedIcon = group.getAvatarSymbolIfMatching(senderName,
+                    symbol, mLayoutColor);
+            if (cachedIcon != null) {
+                cachedAvatars.put(senderName, cachedIcon);
+            }
+        }
+
+        for (int i = 0; i < mGroups.size(); i++) {
+            // Let's now set the avatars
+            MessagingGroup group = mGroups.get(i);
+            CharSequence senderName = group.getSenderName();
+            if (TextUtils.isEmpty(senderName)) {
+                continue;
+            }
+            if (mIsOneToOne && mLargeIcon != null) {
+                group.setAvatar(mLargeIcon);
+            } else {
+                Icon cachedIcon = cachedAvatars.get(senderName);
+                if (cachedIcon == null) {
+                    cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
+                            mLayoutColor);
+                    cachedAvatars.put(senderName, cachedIcon);
+                }
+                group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
+                        mLayoutColor);
+            }
+        }
+    }
+
+    public Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
+        Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        float radius = mAvatarSize / 2.0f;
+        int color = findColor(senderName, layoutColor);
+        mPaint.setColor(color);
+        canvas.drawCircle(radius, radius, radius, mPaint);
+        boolean needDarkText  = ColorUtils.calculateLuminance(color) > 0.5f;
+        mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+        mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.75f : mAvatarSize * 0.4f);
+        int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
+        canvas.drawText(symbol, radius, yPos, mTextPaint);
+        return Icon.createWithBitmap(bitmap);
+    }
+
+    private int findColor(CharSequence senderName, int layoutColor) {
+        double luminance = NotificationColorUtil.calculateLuminance(layoutColor);
+        float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+        // we need to offset the range if the luminance is too close to the borders
+        shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+        shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+        return NotificationColorUtil.getShiftedColor(layoutColor,
+                (int) (shift * COLOR_SHIFT_AMOUNT));
+    }
+
+    private String findNameSplit(String existingName) {
+        String[] split = existingName.split(" ");
+        if (split.length > 1) {
+            return Character.toString(split[0].charAt(0))
+                    + Character.toString(split[1].charAt(0));
+        }
+        return existingName.substring(0, 1);
+    }
+
+    @RemotableViewMethod
+    public void setLayoutColor(int color) {
+        mLayoutColor = color;
+    }
+
+    @RemotableViewMethod
+    public void setIsOneToOne(boolean oneToOne) {
+        mIsOneToOne = oneToOne;
+    }
+
+    private void addMessagesToGroups(List<MessagingMessage> historicMessages,
+            List<MessagingMessage> messages) {
+        // Let's first find our groups!
+        List<List<MessagingMessage>> groups = new ArrayList<>();
+        List<CharSequence> senders = new ArrayList<>();
+
+        // Lets first find the groups
+        findGroups(historicMessages, messages, groups, senders);
+
+        // Let's now create the views and reorder them accordingly
+        createGroupViews(groups, senders);
+    }
+
+    private void createGroupViews(List<List<MessagingMessage>> groups, List<CharSequence> senders) {
+        mGroups.clear();
+        for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
+            List<MessagingMessage> group = groups.get(groupIndex);
+            MessagingGroup newGroup = null;
+            // we'll just take the first group that exists or create one there is none
+            for (int messageIndex = group.size() - 1; messageIndex >= 0; messageIndex--) {
+                MessagingMessage message = group.get(messageIndex);
+                newGroup = message.getGroup();
+                if (newGroup != null) {
+                    break;
+                }
+            }
+            if (newGroup == null) {
+                newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
+                mAddedGroups.add(newGroup);
+            }
+            newGroup.setLayoutColor(mLayoutColor);
+            newGroup.setSender(senders.get(groupIndex));
+            mGroups.add(newGroup);
+
+            if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) {
+                mMessagingLinearLayout.removeView(newGroup);
+                mMessagingLinearLayout.addView(newGroup, groupIndex);
+            }
+            newGroup.setMessages(group);
+        }
+    }
+
+    private void findGroups(List<MessagingMessage> historicMessages,
+            List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+            List<CharSequence> senders) {
+        CharSequence currentSender = null;
+        List<MessagingMessage> currentGroup = null;
+        int histSize = historicMessages.size();
+        for (int i = 0; i < histSize + messages.size(); i++) {
+            MessagingMessage message;
+            if (i < histSize) {
+                message = historicMessages.get(i);
+            } else {
+                message = messages.get(i - histSize);
+            }
+            boolean isNewGroup = currentGroup == null;
+            CharSequence sender = message.getMessage().getSender();
+            isNewGroup |= !TextUtils.equals(sender, currentSender);
+            if (isNewGroup) {
+                currentGroup = new ArrayList<>();
+                groups.add(currentGroup);
+                senders.add(sender);
+                currentSender = sender;
+            }
+            currentGroup.add(message);
+        }
+    }
+
+    private void updateContractedMessage() {
+        for (int i = mMessages.size() - 1; i >= 0; i--) {
+            MessagingMessage m = mMessages.get(i);
+            // Incoming messages have a non-empty sender.
+            if (!TextUtils.isEmpty(m.getMessage().getSender())) {
+                mContractedMessage = m;
+                return;
+            }
+        }
+        if (!mMessages.isEmpty()) {
+            // No incoming messages, fall back to outgoing message
+            mContractedMessage = mMessages.get(mMessages.size() - 1);
+            return;
+        }
+        mContractedMessage = null;
+    }
+
+    /**
+     * Creates new messages, reusing existing ones if they are available.
+     *
+     * @param newMessages the messages to parse.
+     */
+    private List<MessagingMessage> createMessages(
+            List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+        List<MessagingMessage> result = new ArrayList<>();;
+        for (int i = 0; i < newMessages.size(); i++) {
+            Notification.MessagingStyle.Message m = newMessages.get(i);
+            MessagingMessage message = findAndRemoveMatchingMessage(m);
+            if (message == null) {
+                message = MessagingMessage.createMessage(this, m);
+                message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
+            }
+            message.setIsHistoric(historic);
+            result.add(message);
+        }
+        return result;
+    }
+
+    private MessagingMessage findAndRemoveMatchingMessage(Notification.MessagingStyle.Message m) {
+        for (int i = 0; i < mMessages.size(); i++) {
+            MessagingMessage existing = mMessages.get(i);
+            if (existing.sameAs(m)) {
+                mMessages.remove(i);
+                return existing;
+            }
+        }
+        for (int i = 0; i < mHistoricMessages.size(); i++) {
+            MessagingMessage existing = mHistoricMessages.get(i);
+            if (existing.sameAs(m)) {
+                mHistoricMessages.remove(i);
+                return existing;
+            }
+        }
+        return null;
+    }
+
+    public void showHistoricMessages(boolean show) {
+        mShowHistoricMessages = show;
+        updateHistoricMessageVisibility();
+    }
+
+    private void updateHistoricMessageVisibility() {
+        for (int i = 0; i < mHistoricMessages.size(); i++) {
+            MessagingMessage existing = mHistoricMessages.get(i);
+            existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (!mAddedGroups.isEmpty()) {
+            getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    for (MessagingGroup group : mAddedGroups) {
+                        if (!group.isShown()) {
+                            continue;
+                        }
+                        MessagingPropertyAnimator.fadeIn(group.getAvatar());
+                        MessagingPropertyAnimator.fadeIn(group.getSender());
+                        MessagingPropertyAnimator.startLocalTranslationFrom(group,
+                                group.getHeight(), LINEAR_OUT_SLOW_IN);
+                    }
+                    mAddedGroups.clear();
+                    getViewTreeObserver().removeOnPreDrawListener(this);
+                    return true;
+                }
+            });
+        }
+    }
+
+    public View getContractedMessage() {
+        return mContractedMessage;
+    }
+
+    public MessagingLinearLayout getMessagingLinearLayout() {
+        return mMessagingLinearLayout;
+    }
+
+    public ArrayList<MessagingGroup> getMessagingGroups() {
+        return mGroups;
+    }
+}
diff --git a/com/android/internal/widget/MessagingLinearLayout.java b/com/android/internal/widget/MessagingLinearLayout.java
index 70473a0..f0ef370 100644
--- a/com/android/internal/widget/MessagingLinearLayout.java
+++ b/com/android/internal/widget/MessagingLinearLayout.java
@@ -36,28 +36,14 @@
 @RemoteViews.RemoteView
 public class MessagingLinearLayout extends ViewGroup {
 
-    private static final int NOT_MEASURED_BEFORE = -1;
     /**
      * Spacing to be applied between views.
      */
     private int mSpacing;
 
-    /**
-     * The maximum height allowed.
-     */
-    private int mMaxHeight;
+    private int mMaxDisplayedLines = Integer.MAX_VALUE;
 
-    private int mIndentLines;
-
-    /**
-     * Id of the child that's also visible in the contracted layout.
-     */
-    private int mContractedChildId;
-    /**
-     * The last measured with in a layout pass if it was measured before or
-     * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass.
-     */
-    private int mLastMeasuredWidth = NOT_MEASURED_BEFORE;
+    private MessagingLayout mMessagingLayout;
 
     public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -79,7 +65,6 @@
         a.recycle();
     }
 
-
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // This is essentially a bottom-up linear layout that only adds children that fit entirely
@@ -91,118 +76,67 @@
                 break;
         }
         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-        boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE
-                || getMeasuredHeight() != targetHeight
-                || mLastMeasuredWidth != widthSize;
-
-        final int count = getChildCount();
-        if (recalculateVisibility) {
-            // We only need to recalculate the view visibilities if the view wasn't measured already
-            // in this pass, otherwise we may drop messages here already since we are measured
-            // exactly with what we returned before, which was optimized already with the
-            // line-indents.
-            for (int i = 0; i < count; ++i) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                lp.hide = true;
-            }
-
-            int totalHeight = mPaddingTop + mPaddingBottom;
-            boolean first = true;
-
-            // Starting from the bottom: we measure every view as if it were the only one. If it still
-
-            // fits, we take it, otherwise we stop there.
-            for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
-                if (getChildAt(i).getVisibility() == GONE) {
-                    continue;
-                }
-                final View child = getChildAt(i);
-                LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
-                ImageFloatingTextView textChild = null;
-                if (child instanceof ImageFloatingTextView) {
-                    // Pretend we need the image padding for all views, we don't know which
-                    // one will end up needing to do this (might end up not using all the space,
-                    // but calculating this exactly would be more expensive).
-                    textChild = (ImageFloatingTextView) child;
-                    textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines);
-                }
-
-                int spacing = first ? 0 : mSpacing;
-                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
-                        - mPaddingTop - mPaddingBottom + spacing);
-
-                final int childHeight = child.getMeasuredHeight();
-                int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
-                        lp.bottomMargin + spacing);
-                first = false;
-                boolean measuredTooSmall = false;
-                if (textChild != null) {
-                    measuredTooSmall = childHeight < textChild.getLayoutHeight()
-                            + textChild.getPaddingTop() + textChild.getPaddingBottom();
-                }
-
-                if (newHeight <= targetHeight && !measuredTooSmall) {
-                    totalHeight = newHeight;
-                    lp.hide = false;
-                } else {
-                    break;
-                }
-            }
-        }
 
         // Now that we know which views to take, fix up the indents and see what width we get.
         int measuredWidth = mPaddingLeft + mPaddingRight;
-        int imageLines = mIndentLines;
-        // Need to redo the height because it may change due to changing indents.
-        int totalHeight = mPaddingTop + mPaddingBottom;
-        boolean first = true;
-        for (int i = 0; i < count; i++) {
+        final int count = getChildCount();
+        int totalHeight;
+        for (int i = 0; i < count; ++i) {
             final View child = getChildAt(i);
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            if (child.getVisibility() == GONE || lp.hide) {
-                continue;
-            }
-
-            if (child instanceof ImageFloatingTextView) {
-                ImageFloatingTextView textChild = (ImageFloatingTextView) child;
-                if (imageLines == 2 && textChild.getLineCount() > 2) {
-                    // HACK: If we need indent for two lines, and they're coming from the same
-                    // view, we need extra spacing to compensate for the lack of margins,
-                    // so add an extra line of indent.
-                    imageLines = 3;
-                }
-                boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
-                if (changed || !recalculateVisibility) {
-                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
-                            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
-                            lp.width);
-                    // we want to measure it at most as high as it is currently, otherwise we'll
-                    // drop later lines
-                    final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
-                            targetHeight - child.getMeasuredHeight(), lp.height);
-
-                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);;
-                }
-                imageLines -= textChild.getLineCount();
-            }
-
-            measuredWidth = Math.max(measuredWidth,
-                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
-                            + mPaddingLeft + mPaddingRight);
-            totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
-                    lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
-            first = false;
+            lp.hide = true;
         }
 
+        totalHeight = mPaddingTop + mPaddingBottom;
+        boolean first = true;
+        int linesRemaining = mMaxDisplayedLines;
+
+        // Starting from the bottom: we measure every view as if it were the only one. If it still
+        // fits, we take it, otherwise we stop there.
+        for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
+            if (getChildAt(i).getVisibility() == GONE) {
+                continue;
+            }
+            final View child = getChildAt(i);
+            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+            MessagingChild messagingChild = null;
+            if (child instanceof MessagingChild) {
+                messagingChild = (MessagingChild) child;
+                messagingChild.setMaxDisplayedLines(linesRemaining);
+            }
+            int spacing = first ? 0 : mSpacing;
+            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
+                    - mPaddingTop - mPaddingBottom + spacing);
+
+            final int childHeight = child.getMeasuredHeight();
+            int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
+                    lp.bottomMargin + spacing);
+            first = false;
+            int measureType = MessagingChild.MEASURED_NORMAL;
+            if (messagingChild != null) {
+                measureType = messagingChild.getMeasuredType();
+                linesRemaining -= messagingChild.getConsumedLines();
+            }
+            boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED;
+            boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL;
+            if (newHeight <= targetHeight && !isTooSmall) {
+                totalHeight = newHeight;
+                measuredWidth = Math.max(measuredWidth,
+                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
+                                + mPaddingLeft + mPaddingRight);
+                lp.hide = false;
+                if (isShortened || linesRemaining <= 0) {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
 
         setMeasuredDimension(
                 resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
                         widthMeasureSpec),
-                resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
-                        heightMeasureSpec));
-        mLastMeasuredWidth = widthSize;
+                Math.max(getSuggestedMinimumHeight(), totalHeight));
     }
 
     @Override
@@ -221,14 +155,23 @@
         childTop = mPaddingTop;
 
         boolean first = true;
-
+        final boolean shown = isShown();
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-            if (child.getVisibility() == GONE || lp.hide) {
+            if (child.getVisibility() == GONE) {
                 continue;
             }
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            MessagingChild messagingChild = (MessagingChild) child;
+            if (lp.hide) {
+                if (shown && lp.visibleBefore) {
+                    messagingChild.hideAnimated();
+                }
+                lp.visibleBefore = false;
+                continue;
+            } else {
+                lp.visibleBefore = true;
+            }
 
             final int childWidth = child.getMeasuredWidth();
             final int childHeight = child.getMeasuredHeight();
@@ -251,14 +194,16 @@
 
             first = false;
         }
-        mLastMeasuredWidth = NOT_MEASURED_BEFORE;
     }
 
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
         if (lp.hide) {
-            return true;
+            MessagingChild messagingChild = (MessagingChild) child;
+            if (!messagingChild.isHidingAnimated()) {
+                return true;
+            }
         }
         return super.drawChild(canvas, child, drawingTime);
     }
@@ -284,31 +229,37 @@
     }
 
     /**
-     * Sets how many lines should be indented to avoid a floating image.
+     * Sets how many lines should be displayed at most
      */
     @RemotableViewMethod
-    public void setNumIndentLines(int numberLines) {
-        mIndentLines = numberLines;
+    public void setMaxDisplayedLines(int numberLines) {
+        mMaxDisplayedLines = numberLines;
     }
 
-    /**
-     * Set id of the child that's also visible in the contracted layout.
-     */
-    @RemotableViewMethod
-    public void setContractedChildId(int contractedChildId) {
-        mContractedChildId = contractedChildId;
+    public void setMessagingLayout(MessagingLayout layout) {
+        mMessagingLayout = layout;
     }
 
-    /**
-     * Get id of the child that's also visible in the contracted layout.
-     */
-    public int getContractedChildId() {
-        return mContractedChildId;
+    public MessagingLayout getMessagingLayout() {
+        return mMessagingLayout;
+    }
+
+    public interface MessagingChild {
+        int MEASURED_NORMAL = 0;
+        int MEASURED_SHORTENED = 1;
+        int MEASURED_TOO_SMALL = 2;
+
+        int getMeasuredType();
+        int getConsumedLines();
+        void setMaxDisplayedLines(int lines);
+        void hideAnimated();
+        boolean isHidingAnimated();
     }
 
     public static class LayoutParams extends MarginLayoutParams {
 
-        boolean hide = false;
+        public boolean hide = false;
+        public boolean visibleBefore = false;
 
         public LayoutParams(Context c, AttributeSet attrs) {
             super(c, attrs);
diff --git a/com/android/internal/widget/MessagingMessage.java b/com/android/internal/widget/MessagingMessage.java
new file mode 100644
index 0000000..f09621f
--- /dev/null
+++ b/com/android/internal/widget/MessagingMessage.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Objects;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingMessage extends ImageFloatingTextView implements
+        MessagingLinearLayout.MessagingChild {
+
+    private static Pools.SimplePool<MessagingMessage> sInstancePool
+            = new Pools.SynchronizedPool<>(10);
+    private Notification.MessagingStyle.Message mMessage;
+    private MessagingGroup mGroup;
+    private boolean mIsHistoric;
+    private boolean mIsHidingAnimated;
+
+    public MessagingMessage(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    private void setMessage(Notification.MessagingStyle.Message message) {
+        mMessage = message;
+        setText(message.getText());
+    }
+
+    public Notification.MessagingStyle.Message getMessage() {
+        return mMessage;
+    }
+
+    boolean sameAs(Notification.MessagingStyle.Message message) {
+        if (!Objects.equals(message.getText(), mMessage.getText())) {
+            return false;
+        }
+        if (!Objects.equals(message.getSender(), mMessage.getSender())) {
+            return false;
+        }
+        if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
+            return false;
+        }
+        return true;
+    }
+
+    boolean sameAs(MessagingMessage message) {
+        return sameAs(message.getMessage());
+    }
+
+    static MessagingMessage createMessage(MessagingLayout layout,
+            Notification.MessagingStyle.Message m) {
+        MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+        MessagingMessage createdMessage = sInstancePool.acquire();
+        if (createdMessage == null) {
+            createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
+                    R.layout.notification_template_messaging_message, messagingLinearLayout,
+                    false);
+        }
+        createdMessage.setMessage(m);
+        return createdMessage;
+    }
+
+    public void removeMessage() {
+        mGroup.removeMessage(this);
+    }
+
+    public void recycle() {
+        mGroup = null;
+        mMessage = null;
+        setAlpha(1.0f);
+        setTranslationY(0);
+        sInstancePool.release(this);
+    }
+
+    public void setMessagingGroup(MessagingGroup group) {
+        mGroup = group;
+    }
+
+    public static void dropCache() {
+        sInstancePool = new Pools.SynchronizedPool<>(10);
+    }
+
+    public void setIsHistoric(boolean isHistoric) {
+        mIsHistoric = isHistoric;
+    }
+
+    public MessagingGroup getGroup() {
+        return mGroup;
+    }
+
+    @Override
+    public int getMeasuredType() {
+        boolean measuredTooSmall = getMeasuredHeight()
+                < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+        if (measuredTooSmall) {
+            return MEASURED_TOO_SMALL;
+        } else {
+            Layout layout = getLayout();
+            if (layout == null) {
+                return MEASURED_TOO_SMALL;
+            }
+            if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
+                return MEASURED_SHORTENED;
+            } else {
+                return MEASURED_NORMAL;
+            }
+        }
+    }
+
+    @Override
+    public void hideAnimated() {
+        setIsHidingAnimated(true);
+        mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
+    }
+
+    private void setIsHidingAnimated(boolean isHiding) {
+        ViewParent parent = getParent();
+        mIsHidingAnimated = isHiding;
+        invalidate();
+        if (parent instanceof ViewGroup) {
+            ((ViewGroup) parent).invalidate();
+        }
+    }
+
+    @Override
+    public boolean isHidingAnimated() {
+        return mIsHidingAnimated;
+    }
+
+    @Override
+    public void setMaxDisplayedLines(int lines) {
+        setMaxLines(lines);
+    }
+
+    @Override
+    public int getConsumedLines() {
+        return getLineCount();
+    }
+
+    public int getLayoutHeight() {
+        Layout layout = getLayout();
+        if (layout == null) {
+            return 0;
+        }
+        return layout.getHeight();
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+}
diff --git a/com/android/internal/widget/MessagingPropertyAnimator.java b/com/android/internal/widget/MessagingPropertyAnimator.java
new file mode 100644
index 0000000..7c3ab7f
--- /dev/null
+++ b/com/android/internal/widget/MessagingPropertyAnimator.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.IntProperty;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.internal.R;
+
+/**
+ * A listener that automatically starts animations when the layout bounds change.
+ */
+public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
+    static final long APPEAR_ANIMATION_LENGTH = 210;
+    private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+    public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+    private static final int TAG_LOCAL_TRANSLATION_ANIMATOR = R.id.tag_local_translation_y_animator;
+    private static final int TAG_LOCAL_TRANSLATION_Y = R.id.tag_local_translation_y;
+    private static final int TAG_LAYOUT_TOP = R.id.tag_layout_top;
+    private static final int TAG_ALPHA_ANIMATOR = R.id.tag_alpha_animator;
+    private static final ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS =
+            view -> view.getId() == com.android.internal.R.id.notification_messaging;
+    private static final IntProperty<View> LOCAL_TRANSLATION_Y =
+            new IntProperty<View>("localTranslationY") {
+                @Override
+                public void setValue(View object, int value) {
+                    setLocalTranslationY(object, value);
+                }
+
+                @Override
+                public Integer get(View object) {
+                    return getLocalTranslationY(object);
+                }
+            };
+
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+            int oldTop, int oldRight, int oldBottom) {
+        int oldHeight = oldBottom - oldTop;
+        Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+        if (layoutTop != null) {
+            oldTop = layoutTop;
+        }
+        int topChange = oldTop - top;
+        if (oldHeight == 0 || topChange == 0 || !v.isShown() || isGone(v)) {
+            // First layout
+            return;
+        }
+        if (layoutTop != null) {
+            v.setTagInternal(TAG_LAYOUT_TOP, top);
+        }
+        int newHeight = bottom - top;
+        int heightDifference = oldHeight - newHeight;
+        // Only add the difference if the height changes and it's getting smaller
+        heightDifference = Math.max(heightDifference, 0);
+        startLocalTranslationFrom(v, topChange + heightDifference + getLocalTranslationY(v));
+    }
+
+    private boolean isGone(View view) {
+        if (view.getVisibility() == View.GONE) {
+            return true;
+        }
+        final ViewGroup.LayoutParams lp = view.getLayoutParams();
+        if (lp instanceof MessagingLinearLayout.LayoutParams
+                && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+            return true;
+        }
+        return false;
+    }
+
+    public static void startLocalTranslationFrom(View v, int startTranslation) {
+        startLocalTranslationFrom(v, startTranslation, MessagingLayout.FAST_OUT_SLOW_IN);
+    }
+
+    public static void startLocalTranslationFrom(View v, int startTranslation,
+            Interpolator interpolator) {
+        startLocalTranslation(v, startTranslation, 0, interpolator);
+    }
+
+    public static void startLocalTranslationTo(View v, int endTranslation,
+            Interpolator interpolator) {
+        startLocalTranslation(v, getLocalTranslationY(v), endTranslation, interpolator);
+    }
+
+    public static int getLocalTranslationY(View v) {
+        Integer tag = (Integer) v.getTag(TAG_LOCAL_TRANSLATION_Y);
+        if (tag == null) {
+            return 0;
+        }
+        return tag;
+    }
+
+    private static void setLocalTranslationY(View v, int value) {
+        v.setTagInternal(TAG_LOCAL_TRANSLATION_Y, value);
+        updateTopAndBottom(v);
+    }
+
+    private static void updateTopAndBottom(View v) {
+        int layoutTop = (int) v.getTag(TAG_LAYOUT_TOP);
+        int localTranslation = getLocalTranslationY(v);
+        int height = v.getHeight();
+        v.setTop(layoutTop + localTranslation);
+        v.setBottom(layoutTop + height + localTranslation);
+    }
+
+    private static void startLocalTranslation(final View v, int start, int end,
+            Interpolator interpolator) {
+        ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR);
+        if (existing != null) {
+            existing.cancel();
+        }
+        ObjectAnimator animator = ObjectAnimator.ofInt(v, LOCAL_TRANSLATION_Y, start, end);
+        Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+        if (layoutTop == null) {
+            layoutTop = v.getTop();
+            v.setTagInternal(TAG_LAYOUT_TOP, layoutTop);
+        }
+        setLocalTranslationY(v, start);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(APPEAR_ANIMATION_LENGTH);
+        animator.addListener(new AnimatorListenerAdapter() {
+            public boolean mCancelled;
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, null);
+                setClippingDeactivated(v, false);
+                if (!mCancelled) {
+                    setLocalTranslationY(v, 0);
+                    v.setTagInternal(TAG_LAYOUT_TOP, null);
+                }
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+        });
+        setClippingDeactivated(v, true);
+        v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, animator);
+        animator.start();
+    }
+
+    public static void fadeIn(final View v) {
+        ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_ALPHA_ANIMATOR);
+        if (existing != null) {
+            existing.cancel();
+        }
+        if (v.getVisibility() == View.INVISIBLE) {
+            v.setVisibility(View.VISIBLE);
+        }
+        ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.ALPHA,
+                0.0f, 1.0f);
+        v.setAlpha(0.0f);
+        animator.setInterpolator(ALPHA_IN);
+        animator.setDuration(APPEAR_ANIMATION_LENGTH);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                v.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+                updateLayerType(v, false /* animating */);
+            }
+        });
+        updateLayerType(v, true /* animating */);
+        v.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+        animator.start();
+    }
+
+    private static void updateLayerType(View view, boolean animating) {
+        if (view.hasOverlappingRendering() && animating) {
+            view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
+            view.setLayerType(View.LAYER_TYPE_NONE, null);
+        }
+    }
+
+    public static void fadeOut(final View view, Runnable endAction) {
+        ObjectAnimator existing = (ObjectAnimator) view.getTag(TAG_ALPHA_ANIMATOR);
+        if (existing != null) {
+            existing.cancel();
+        }
+        ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA,
+                view.getAlpha(), 0.0f);
+        animator.setInterpolator(ALPHA_OUT);
+        animator.setDuration(APPEAR_ANIMATION_LENGTH);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                view.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+                updateLayerType(view, false /* animating */);
+                if (endAction != null) {
+                    endAction.run();
+                }
+            }
+        });
+        updateLayerType(view, true /* animating */);
+        view.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+        animator.start();
+    }
+
+    public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
+        ViewClippingUtil.setClippingDeactivated(transformedView, deactivated,
+                CLIPPING_PARAMETERS);
+    }
+
+    public static boolean isAnimatingTranslation(View v) {
+        return v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR) != null;
+    }
+
+    public static boolean isAnimatingAlpha(View v) {
+        return v.getTag(TAG_ALPHA_ANIMATOR) != null;
+    }
+}
diff --git a/com/android/internal/widget/NotificationActionListLayout.java b/com/android/internal/widget/NotificationActionListLayout.java
index 073aac5..26023b4 100644
--- a/com/android/internal/widget/NotificationActionListLayout.java
+++ b/com/android/internal/widget/NotificationActionListLayout.java
@@ -16,7 +16,10 @@
 
 package com.android.internal.widget;
 
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Pair;
@@ -37,6 +40,7 @@
 @RemoteViews.RemoteView
 public class NotificationActionListLayout extends LinearLayout {
 
+    private final int mGravity;
     private int mTotalWidth = 0;
     private ArrayList<Pair<Integer, TextView>> mMeasureOrderTextViews = new ArrayList<>();
     private ArrayList<View> mMeasureOrderOther = new ArrayList<>();
@@ -45,7 +49,20 @@
     private Drawable mDefaultBackground;
 
     public NotificationActionListLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
+    }
+
+    public NotificationActionListLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public NotificationActionListLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        int[] attrIds = { android.R.attr.gravity };
+        TypedArray ta = context.obtainStyledAttributes(attrs, attrIds, defStyleAttr, defStyleRes);
+        mGravity = ta.getInt(0, 0);
+        ta.recycle();
     }
 
     @Override
@@ -95,6 +112,7 @@
 
         final boolean constrained =
                 MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED;
+        final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
 
         final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
         final int otherSize = mMeasureOrderOther.size();
@@ -137,7 +155,7 @@
 
         // Make sure to measure the last child full-width if we didn't use up the entire width,
         // or we didn't measure yet because there's just one child.
-        if (lastNotGoneChild != null && (constrained && usedWidth < innerWidth
+        if (lastNotGoneChild != null && !centerAligned && (constrained && usedWidth < innerWidth
                 || notGoneChildren == 1)) {
             MarginLayoutParams lp = (MarginLayoutParams) lastNotGoneChild.getLayoutParams();
             if (notGoneChildren > 1) {
@@ -201,9 +219,10 @@
         }
         final boolean isLayoutRtl = isLayoutRtl();
         final int paddingTop = mPaddingTop;
+        final boolean centerAligned = (mGravity & Gravity.CENTER_HORIZONTAL) != 0;
 
         int childTop;
-        int childLeft;
+        int childLeft = centerAligned ? left + (right - left) / 2 - mTotalWidth / 2 : 0;
 
         // Where bottom of child should go
         final int height = bottom - top;
@@ -216,13 +235,12 @@
         final int layoutDirection = getLayoutDirection();
         switch (Gravity.getAbsoluteGravity(Gravity.START, layoutDirection)) {
             case Gravity.RIGHT:
-                // mTotalWidth contains the padding already
-                childLeft = mPaddingLeft + right - left - mTotalWidth;
+                childLeft += mPaddingLeft + right - left - mTotalWidth;
                 break;
 
             case Gravity.LEFT:
             default:
-                childLeft = mPaddingLeft;
+                childLeft += mPaddingLeft;
                 break;
         }
 
diff --git a/com/android/internal/widget/RemeasuringLinearLayout.java b/com/android/internal/widget/RemeasuringLinearLayout.java
new file mode 100644
index 0000000..e352b45
--- /dev/null
+++ b/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+/**
+ * A LinearLayout that sets it's height again after the last measure pass. This is needed for
+ * MessagingLayouts where groups need to be able to snap it's height to.
+ */
+@RemoteViews.RemoteView
+public class RemeasuringLinearLayout extends LinearLayout {
+
+    public RemeasuringLinearLayout(Context context) {
+        super(context);
+    }
+
+    public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public RemeasuringLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int count = getChildCount();
+        int height = 0;
+        for (int i = 0; i < count; ++i) {
+            final View child = getChildAt(i);
+            if (child == null || child.getVisibility() == View.GONE) {
+                continue;
+            }
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            height = Math.max(height, height + child.getMeasuredHeight() + lp.topMargin +
+                    lp.bottomMargin);
+        }
+        setMeasuredDimension(getMeasuredWidth(), height);
+    }
+}
diff --git a/com/android/internal/widget/ViewClippingUtil.java b/com/android/internal/widget/ViewClippingUtil.java
new file mode 100644
index 0000000..59bbed4
--- /dev/null
+++ b/com/android/internal/widget/ViewClippingUtil.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.util.ArraySet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import com.android.internal.R;
+
+/**
+ * A utility class that allows to clip views and their parents to allow for better transitions
+ */
+public class ViewClippingUtil {
+    private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
+    private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
+    private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+
+    public static void setClippingDeactivated(final View transformedView, boolean deactivated,
+        ClippingParameters clippingParameters) {
+        if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+            return;
+        }
+        if (!(transformedView.getParent() instanceof ViewGroup)) {
+            return;
+        }
+        ViewGroup parent = (ViewGroup) transformedView.getParent();
+        while (true) {
+            if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+                return;
+            }
+            ArraySet<View> clipSet = (ArraySet<View>) parent.getTag(CLIP_CLIPPING_SET);
+            if (clipSet == null) {
+                clipSet = new ArraySet<>();
+                parent.setTagInternal(CLIP_CLIPPING_SET, clipSet);
+            }
+            Boolean clipChildren = (Boolean) parent.getTag(CLIP_CHILDREN_TAG);
+            if (clipChildren == null) {
+                clipChildren = parent.getClipChildren();
+                parent.setTagInternal(CLIP_CHILDREN_TAG, clipChildren);
+            }
+            Boolean clipToPadding = (Boolean) parent.getTag(CLIP_TO_PADDING);
+            if (clipToPadding == null) {
+                clipToPadding = parent.getClipToPadding();
+                parent.setTagInternal(CLIP_TO_PADDING, clipToPadding);
+            }
+            if (!deactivated) {
+                clipSet.remove(transformedView);
+                if (clipSet.isEmpty()) {
+                    parent.setClipChildren(clipChildren);
+                    parent.setClipToPadding(clipToPadding);
+                    parent.setTagInternal(CLIP_CLIPPING_SET, null);
+                    clippingParameters.onClippingStateChanged(parent, true);
+                }
+            } else {
+                clipSet.add(transformedView);
+                parent.setClipChildren(false);
+                parent.setClipToPadding(false);
+                clippingParameters.onClippingStateChanged(parent, false);
+            }
+            if (clippingParameters.shouldFinish(parent)) {
+                return;
+            }
+            final ViewParent viewParent = parent.getParent();
+            if (viewParent instanceof ViewGroup) {
+                parent = (ViewGroup) viewParent;
+            } else {
+                return;
+            }
+        }
+    }
+
+    public interface ClippingParameters {
+        /**
+         * Should we stop clipping at this view? If true is returned, {@param view} is the last view
+         * where clipping is activated / deactivated.
+         */
+        boolean shouldFinish(View view);
+
+        /**
+         * Is it allowed to enable clipping on this view.
+         */
+        default boolean isClippingEnablingAllowed(View view) {
+            return !MessagingPropertyAnimator.isAnimatingTranslation(view);
+        }
+
+        /**
+         * A method that is called whenever the view starts clipping again / stops clipping to the
+         * children and padding.
+         */
+        default void onClippingStateChanged(View view, boolean isClipping) {};
+    }
+}
diff --git a/com/android/keyguard/KeyguardSliceView.java b/com/android/keyguard/KeyguardSliceView.java
new file mode 100644
index 0000000..c18f9b6
--- /dev/null
+++ b/com/android/keyguard/KeyguardSliceView.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.keyguard;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+
+/**
+ * View visible under the clock on the lock screen and AoD.
+ */
+public class KeyguardSliceView extends LinearLayout {
+
+    private final Uri mKeyguardSliceUri;
+    private TextView mTitle;
+    private TextView mText;
+    private Slice mSlice;
+    private PendingIntent mSliceAction;
+    private int mTextColor;
+    private float mDarkAmount = 0;
+
+    private final ContentObserver mObserver;
+
+    public KeyguardSliceView(Context context) {
+        this(context, null, 0);
+    }
+
+    public KeyguardSliceView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mObserver = new KeyguardSliceObserver(new Handler());
+        mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTitle = findViewById(R.id.title);
+        mText = findViewById(R.id.text);
+        mTextColor = mTitle.getCurrentTextColor();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        // Set initial content
+        showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+
+        // Make sure we always have the most current slice
+        getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
+                false /* notifyDescendants */, mObserver);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        getContext().getContentResolver().unregisterContentObserver(mObserver);
+    }
+
+    private void showSlice(Slice slice) {
+        // Items will be wrapped into an action when they have tap targets.
+        SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+        if (actionSlice != null) {
+            mSlice = actionSlice.getSlice();
+            mSliceAction = actionSlice.getAction();
+        } else {
+            mSlice = slice;
+            mSliceAction = null;
+        }
+
+        if (mSlice == null) {
+            setVisibility(GONE);
+            return;
+        }
+
+        SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+        if (title == null) {
+            mTitle.setVisibility(GONE);
+        } else {
+            mTitle.setVisibility(VISIBLE);
+            mTitle.setText(title.getText());
+        }
+
+        SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+        if (text == null) {
+            mText.setVisibility(GONE);
+        } else {
+            mText.setVisibility(VISIBLE);
+            mText.setText(text.getText());
+        }
+
+        final int visibility = title == null && text == null ? GONE : VISIBLE;
+        if (visibility != getVisibility()) {
+            setVisibility(visibility);
+        }
+    }
+
+    public void setDark(float darkAmount) {
+        mDarkAmount = darkAmount;
+        updateTextColors();
+    }
+
+    public void setTextColor(int textColor) {
+        mTextColor = textColor;
+    }
+
+    private void updateTextColors() {
+        final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+        mTitle.setTextColor(blendedColor);
+        mText.setTextColor(blendedColor);
+    }
+
+    private class KeyguardSliceObserver extends ContentObserver {
+        KeyguardSliceObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            this.onChange(selfChange, null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+        }
+    }
+}
diff --git a/com/android/keyguard/KeyguardStatusView.java b/com/android/keyguard/KeyguardStatusView.java
index bc2a59d..78cf2b9 100644
--- a/com/android/keyguard/KeyguardStatusView.java
+++ b/com/android/keyguard/KeyguardStatusView.java
@@ -19,11 +19,9 @@
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
-import android.graphics.PorterDuff;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -42,8 +40,8 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.settingslib.Utils;
 import com.android.systemui.ChargingView;
-import com.android.systemui.statusbar.policy.DateView;
 
 import java.util.Locale;
 
@@ -55,13 +53,11 @@
     private final LockPatternUtils mLockPatternUtils;
     private final AlarmManager mAlarmManager;
 
-    private TextView mAlarmStatusView;
-    private DateView mDateView;
     private TextClock mClockView;
     private TextView mOwnerInfo;
     private ViewGroup mClockContainer;
     private ChargingView mBatteryDoze;
-    private View mKeyguardStatusArea;
+    private KeyguardSliceView mKeyguardSlice;
     private Runnable mPendingMarqueeStart;
     private Handler mHandler;
 
@@ -69,8 +65,6 @@
     private boolean mPulsing;
     private float mDarkAmount = 0;
     private int mTextColor;
-    private int mDateTextColor;
-    private int mAlarmTextColor;
 
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
 
@@ -141,7 +135,6 @@
 
     private void setEnableMarqueeImpl(boolean enabled) {
         if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
-        if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled);
         if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
     }
 
@@ -149,8 +142,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mClockContainer = findViewById(R.id.keyguard_clock_container);
-        mAlarmStatusView = findViewById(R.id.alarm_status);
-        mDateView = findViewById(R.id.date_view);
         mClockView = findViewById(R.id.clock_view);
         mClockView.setShowCurrentUserTime(true);
         if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) {
@@ -158,11 +149,9 @@
         }
         mOwnerInfo = findViewById(R.id.owner_info);
         mBatteryDoze = findViewById(R.id.battery_doze);
-        mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
-        mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea};
+        mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+        mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
         mTextColor = mClockView.getCurrentTextColor();
-        mDateTextColor = mDateView.getCurrentTextColor();
-        mAlarmTextColor = mAlarmStatusView.getCurrentTextColor();
 
         boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
         setEnableMarquee(shouldMarquee);
@@ -184,8 +173,6 @@
         layoutParams.bottomMargin = getResources().getDimensionPixelSize(
                 R.dimen.bottom_text_spacing_digital);
         mClockView.setLayoutParams(layoutParams);
-        mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
         if (mOwnerInfo != null) {
             mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                     getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
@@ -193,8 +180,6 @@
     }
 
     public void refreshTime() {
-        mDateView.setDatePattern(Patterns.dateViewSkel);
-
         mClockView.setFormat12Hour(Patterns.clockView12);
         mClockView.setFormat24Hour(Patterns.clockView24);
     }
@@ -205,23 +190,11 @@
         Patterns.update(mContext, nextAlarm != null);
 
         refreshTime();
-        refreshAlarmStatus(nextAlarm);
-    }
-
-    void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) {
-        if (nextAlarm != null) {
-            String alarm = formatNextAlarm(mContext, nextAlarm);
-            mAlarmStatusView.setText(alarm);
-            mAlarmStatusView.setContentDescription(
-                    getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm));
-            mAlarmStatusView.setVisibility(View.VISIBLE);
-        } else {
-            mAlarmStatusView.setVisibility(View.GONE);
-        }
     }
 
     public int getClockBottom() {
-        return mKeyguardStatusArea.getBottom();
+        return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom()
+                : mClockView.getBottom();
     }
 
     public float getClockTextSize() {
@@ -341,11 +314,8 @@
 
         updateDozeVisibleViews();
         mBatteryDoze.setDark(dark);
+        mKeyguardSlice.setDark(darkAmount);
         mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
-        mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount));
-        int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount);
-        mAlarmStatusView.setTextColor(blendedAlarmColor);
-        mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor));
     }
 
     public void setPulsing(boolean pulsing) {
diff --git a/com/android/keyguard/KeyguardUpdateMonitor.java b/com/android/keyguard/KeyguardUpdateMonitor.java
index 2bb992c..41b007a 100644
--- a/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -76,8 +76,9 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import com.google.android.collect.Lists;
 
@@ -1187,7 +1188,7 @@
             mFpm.addLockoutResetCallback(mLockoutResetCallback);
         }
 
-        SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         mUserManager = context.getSystemService(UserManager.class);
     }
 
@@ -1738,6 +1739,10 @@
         mFailedAttempts.delete(sCurrentUser);
     }
 
+    public ServiceState getServiceState(int subId) {
+        return mServiceStates.get(subId);
+    }
+
     public int getFailedUnlockAttempts(int userId) {
         return mFailedAttempts.get(userId, 0);
     }
@@ -1772,7 +1777,8 @@
         }
     }
 
-    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+    private final SysUiTaskStackChangeListener
+            mTaskStackListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onTaskStackChangedBackground() {
             try {
diff --git a/com/android/layout/remote/api/RemoteLayoutlibCallback.java b/com/android/layout/remote/api/RemoteLayoutlibCallback.java
index 5c138ef..0f315ca 100644
--- a/com/android/layout/remote/api/RemoteLayoutlibCallback.java
+++ b/com/android/layout/remote/api/RemoteLayoutlibCallback.java
@@ -26,6 +26,7 @@
 import com.android.util.Pair;
 
 import java.io.Serializable;
+import java.nio.file.Path;
 import java.rmi.Remote;
 import java.rmi.RemoteException;
 
@@ -58,14 +59,11 @@
 
     RemoteActionBarCallback getActionBarCallback() throws RemoteException;
 
-    Object loadClass(String name, Class[] constructorSignature, Object[] constructorArgs)
-            throws ClassNotFoundException, RemoteException;
-
     <T> T getFlag(Key<T> key) throws RemoteException;
 
     RemoteParserFactory getParserFactory() throws RemoteException;
 
-    Class<?> findClass(String name) throws ClassNotFoundException, RemoteException;
+    Path findClassPath(String name) throws RemoteException;
 
     RemoteXmlPullParser getXmlFileParser(String fileName) throws RemoteException;
 
diff --git a/com/android/layoutlib/bridge/impl/ResourceHelper.java b/com/android/layoutlib/bridge/impl/ResourceHelper.java
index 45330d0..16f92f3 100644
--- a/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -49,6 +49,7 @@
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.graphics.Typeface_Accessor;
+import android.graphics.Typeface_Delegate;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -387,59 +388,8 @@
             return null;
         }
 
-        // Check if this is an asset that we've already loaded dynamically
-        Typeface typeface = Typeface.findFromCache(context.getAssets(), fontName);
-        if (typeface != null) {
-            return typeface;
-        }
 
-        String lowerCaseValue = fontName.toLowerCase();
-        if (lowerCaseValue.endsWith(".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(fontName);
-            }
-            else {
-                File f = new File(fontName);
-                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 " + fontName,
-                                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(),
-                            fontName);
-                } catch (XmlPullParserException | IOException e) {
-                    Bridge.getLog().error(null, "Failed to parse file " + fontName,
-                            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)", fontName),
-                        null /*data*/);
-            }
-        } else {
-            typeface = Typeface.createFromResources(context.getAssets(), fontName, 0);
-        }
-
-        return typeface;
+        return Typeface_Delegate.createFromDisk(context, fontName, isFramework);
     }
 
     /**
diff --git a/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java b/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
index 43ea569..b9b1a00 100644
--- a/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
+++ b/com/android/layoutlib/bridge/remote/client/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -29,8 +29,13 @@
 import com.android.layout.remote.api.RemoteXmlPullParser;
 import com.android.resources.ResourceType;
 import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
 import com.android.util.Pair;
 
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.rmi.RemoteException;
 import java.rmi.server.UnicastRemoteObject;
 
@@ -113,12 +118,6 @@
     }
 
     @Override
-    public Object loadClass(String name, Class[] constructorSignature, Object[] constructorArgs)
-            throws ClassNotFoundException {
-        throw new UnsupportedOperationException("Not implemented yet");
-    }
-
-    @Override
     public <T> T getFlag(Key<T> key) {
         return mDelegate.getFlag(key);
     }
@@ -132,9 +131,21 @@
         }
     }
 
+    @Nullable
     @Override
-    public Class<?> findClass(String name) throws ClassNotFoundException {
-        throw new UnsupportedOperationException("Not implemented yet");
+    public Path findClassPath(String name) {
+        try {
+            Class<?> clazz = mDelegate.findClass(name);
+            URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
+            if (url != null) {
+                return Paths.get(url.toURI());
+            }
+        } catch (ClassNotFoundException ignore) {
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+
+        return null;
     }
 
     @Override
diff --git a/com/android/layoutlib/bridge/remote/server/ServerMain.java b/com/android/layoutlib/bridge/remote/server/ServerMain.java
index daecdd4..09c27fd 100644
--- a/com/android/layoutlib/bridge/remote/server/ServerMain.java
+++ b/com/android/layoutlib/bridge/remote/server/ServerMain.java
@@ -19,17 +19,31 @@
 import com.android.layout.remote.api.RemoteBridge;
 import com.android.tools.layoutlib.annotations.NotNull;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.rmi.NoSuchObjectException;
 import java.rmi.RemoteException;
 import java.rmi.registry.LocateRegistry;
 import java.rmi.registry.Registry;
 import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * Main server class. The main method will start an RMI server for the {@link RemoteBridgeImpl}
  * class.
  */
 public class ServerMain {
+    private static final String RUNNING_SERVER_STR = "Server is running on port ";
     public static int REGISTRY_BASE_PORT = 9000;
 
     private final int mPort;
@@ -52,13 +66,101 @@
     }
 
     /**
+     * This will start a new JVM and connect to the new JVM RMI registry.
+     * <p/>
      * The server will start looking for ports available for the {@link Registry} until a free one
      * is found. The port number will be returned.
      * If no ports are available, a {@link RemoteException} will be thrown.
      * @param basePort port number to start looking for available ports
      * @param limit number of ports to check. The last port to be checked will be (basePort + limit)
      */
-    public static ServerMain startServer(int basePort, int limit) throws RemoteException {
+    public static ServerMain forkAndStartServer(int basePort, int limit)
+            throws IOException, InterruptedException {
+        // We start a new VM by copying all the parameter that we received in the current VM.
+        // We only remove the agentlib parameter since that could cause a port collision and avoid
+        // the new VM from starting.
+        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
+        List<String> arguments = runtimeMxBean.getInputArguments().stream()
+                .filter(arg -> !arg.contains("-agentlib")) // Filter agentlib to avoid conflicts
+                .collect(Collectors.toList());
+
+        Path javaPath = Paths.get(System.getProperty("java.home"), "bin", "java");
+        String thisClassName = ServerMain.class.getName()
+                .replace('.','/');
+
+        List<String> cmd = new ArrayList<>();
+        cmd.add(javaPath.toString());
+
+        // Inherited arguments
+        cmd.addAll(arguments);
+
+        // Classpath
+        cmd.add("-cp");
+        cmd.add(System.getProperty("java.class.path"));
+
+        // Class name and path
+        cmd.add(thisClassName);
+
+        // ServerMain parameters [basePort. limit]
+        cmd.add(Integer.toString(basePort));
+        cmd.add(Integer.toString(limit));
+
+        Process process = new ProcessBuilder()
+                .command(cmd)
+                .start();
+
+        BlockingQueue<String> outputQueue = new ArrayBlockingQueue<>(10);
+        Thread outputThread = new Thread(() -> {
+            BufferedReader inputStream = new BufferedReader(
+                    new InputStreamReader(process.getInputStream()));
+            inputStream.lines()
+                    .forEach(outputQueue::offer);
+
+        });
+        outputThread.setName("output thread");
+        outputThread.start();
+
+        Runnable killServer = () -> {
+            process.destroyForcibly();
+            outputThread.interrupt();
+            try {
+                outputThread.join();
+            } catch (InterruptedException ignore) {
+            }
+        };
+
+        // Try to read the "Running on port" line in 10 lines. If it's not there just fail.
+        for (int i = 0; i < 10; i++) {
+            String line = outputQueue.poll(1000, TimeUnit.SECONDS);
+
+            if (line.startsWith(RUNNING_SERVER_STR)) {
+                int runningPort = Integer.parseInt(line.substring(RUNNING_SERVER_STR.length()));
+                System.out.println("Running on port " + runningPort);
+
+                // We already know where the server is running so we just need to get the registry
+                // and return our own instance of ServerMain
+                Registry registry = LocateRegistry.getRegistry(runningPort);
+                return new ServerMain(runningPort, registry) {
+                    @Override
+                    public void stop() {
+                        killServer.run();
+                    }
+                };
+            }
+        }
+
+        killServer.run();
+        throw new IOException("Unable to find start string");
+    }
+
+    /**
+     * The server will start looking for ports available for the {@link Registry} until a free one
+     * is found. The port number will be returned.
+     * If no ports are available, a {@link RemoteException} will be thrown.
+     * @param basePort port number to start looking for available ports
+     * @param limit number of ports to check. The last port to be checked will be (basePort + limit)
+     */
+    private static ServerMain startServer(int basePort, int limit) throws RemoteException {
         RemoteBridgeImpl remoteBridge = new RemoteBridgeImpl();
         RemoteBridge stub = (RemoteBridge) UnicastRemoteObject.exportObject(remoteBridge, 0);
 
@@ -81,7 +183,10 @@
     }
 
     public static void main(String[] args) throws RemoteException {
-        ServerMain server = startServer(REGISTRY_BASE_PORT, 10);
-        System.out.println("Server is running on port " + server.getPort());
+        int basePort = args.length > 0 ? Integer.parseInt(args[0]) : REGISTRY_BASE_PORT;
+        int limit = args.length > 1 ? Integer.parseInt(args[1]) : 10;
+
+        ServerMain server = startServer(basePort, limit);
+        System.out.println(RUNNING_SERVER_STR + server.getPort());
     }
 }
diff --git a/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java b/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
index 5729579..b685098 100644
--- a/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
+++ b/com/android/layoutlib/bridge/remote/server/adapters/RemoteLayoutlibCallbackAdapter.java
@@ -26,25 +26,138 @@
 import com.android.ide.common.rendering.api.SessionParams.Key;
 import com.android.layout.remote.api.RemoteLayoutlibCallback;
 import com.android.layout.remote.api.RemoteLayoutlibCallback.RemoteResolveResult;
+import com.android.layoutlib.bridge.MockView;
 import com.android.resources.ResourceType;
 import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
 import com.android.util.Pair;
 
 import org.xmlpull.v1.XmlPullParser;
 
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.rmi.RemoteException;
+import java.util.HashMap;
+import java.util.function.Function;
 
 public class RemoteLayoutlibCallbackAdapter extends LayoutlibCallback {
     private final RemoteLayoutlibCallback mDelegate;
+    private final PathClassLoader mPathClassLoader;
 
     public RemoteLayoutlibCallbackAdapter(@NotNull RemoteLayoutlibCallback remote) {
         mDelegate = remote;
+
+        // Views requested to this callback need to be brought from the "client" side.
+        // We transform any loadView into two operations:
+        //  - First we ask to where the class is located on disk via findClassPath
+        //  - Second, we instantiate the class in the "server" side
+        HashMap<String, Path> nameToPathCache = new HashMap<>();
+        Function<String, Path> getPathFromName = cacheName -> nameToPathCache.computeIfAbsent(
+                cacheName,
+                name -> {
+                    try {
+                        return mDelegate.findClassPath(name);
+                    } catch (RemoteException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+
+        mPathClassLoader = new PathClassLoader(getPathFromName);
+    }
+
+    @NotNull
+    private Object createNewInstance(@NotNull Class<?> clazz,
+            @Nullable Class<?>[] constructorSignature, @Nullable Object[] constructorParameters,
+            boolean isView)
+            throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException,
+            IllegalAccessException, InstantiationException {
+        Constructor<?> constructor = null;
+
+        try {
+            constructor = clazz.getConstructor(constructorSignature);
+        } catch (NoSuchMethodException e) {
+            if (!isView) {
+                throw e;
+            }
+
+            // View class has 1-parameter, 2-parameter and 3-parameter constructors
+
+            final int paramsCount = constructorSignature != null ? constructorSignature.length : 0;
+            if (paramsCount == 0) {
+                throw e;
+            }
+            assert constructorParameters != null;
+
+            for (int i = 3; i >= 1; i--) {
+                if (i == paramsCount) {
+                    continue;
+                }
+
+                final int k = paramsCount < i ? paramsCount : i;
+
+                final Class[] sig = new Class[i];
+                System.arraycopy(constructorSignature, 0, sig, 0, k);
+
+                final Object[] params = new Object[i];
+                System.arraycopy(constructorParameters, 0, params, 0, k);
+
+                for (int j = k + 1; j <= i; j++) {
+                    if (j == 2) {
+                        sig[j - 1] = findClass("android.util.AttributeSet");
+                        params[j - 1] = null;
+                    } else if (j == 3) {
+                        // parameter 3: int defstyle
+                        sig[j - 1] = int.class;
+                        params[j - 1] = 0;
+                    }
+                }
+
+                constructorSignature = sig;
+                constructorParameters = params;
+
+                try {
+                    constructor = clazz.getConstructor(constructorSignature);
+                    if (constructor != null) {
+                        if (constructorSignature.length < 2) {
+                            // TODO: Convert this to remote
+//                            LOG.info("wrong_constructor: Custom view " +
+//                                    clazz.getSimpleName() +
+//                                    " is not using the 2- or 3-argument " +
+//                                    "View constructors; XML attributes will not work");
+//                            mDelegate.warning("wrongconstructor", //$NON-NLS-1$
+//                                    String.format(
+//                                            "Custom view %1$s is not using the 2- or 3-argument
+// View constructors; XML attributes will not work",
+//                                            clazz.getSimpleName()), null, null);
+                        }
+                        break;
+                    }
+                } catch (NoSuchMethodException ignored) {
+                }
+            }
+
+            if (constructor == null) {
+                throw e;
+            }
+        }
+
+        constructor.setAccessible(true);
+        return constructor.newInstance(constructorParameters);
     }
 
     @Override
     public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
             throws Exception {
-        throw new UnsupportedOperationException("Not implemented yet");
+        Class<?> viewClass = MockView.class;
+        try {
+            viewClass = findClass(name);
+        } catch (ClassNotFoundException ignore) {
+            // MockView will be used instead
+        }
+        return createNewInstance(viewClass, constructorSignature, constructorArgs, true);
     }
 
     @Override
@@ -152,7 +265,7 @@
 
     @Override
     public Class<?> findClass(String name) throws ClassNotFoundException {
-        throw new UnsupportedOperationException("Not implemented yet");
+        return mPathClassLoader.loadClass(name);
     }
 
     @Override
@@ -163,4 +276,30 @@
             throw new RuntimeException(e);
         }
     }
+
+    /**
+     * Simple class loaders that loads classes from Paths
+     */
+    private static class PathClassLoader extends ClassLoader {
+        private final Function<String, Path> mGetPath;
+
+        private PathClassLoader(@NotNull Function<String, Path> getUrl) {
+            mGetPath = getUrl;
+        }
+
+        @Override
+        protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
+            Path path = mGetPath.apply(name);
+
+            if (path != null) {
+                try {
+                    byte[] content = Files.readAllBytes(path);
+                    return defineClass(name, content, 0, content.length);
+                } catch (IOException ignore) {
+                }
+            }
+
+            throw new ClassNotFoundException(name);
+        }
+    }
 }
diff --git a/com/android/printspooler/ui/SelectPrinterActivity.java b/com/android/printspooler/ui/SelectPrinterActivity.java
index a9a6cbd..7c2e55f 100644
--- a/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -122,7 +122,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        getActionBar().setIcon(R.drawable.ic_print);
+        getActionBar().setIcon(com.android.internal.R.drawable.ic_print);
 
         setContentView(R.layout.select_printer_activity);
 
diff --git a/com/android/providers/settings/DatabaseHelper.java b/com/android/providers/settings/DatabaseHelper.java
index 1557d91..7428149 100644
--- a/com/android/providers/settings/DatabaseHelper.java
+++ b/com/android/providers/settings/DatabaseHelper.java
@@ -1836,20 +1836,10 @@
         }
 
         if (upgradeVersion < 116) {
-            if (mUserHandle == UserHandle.USER_SYSTEM) {
-                db.beginTransaction();
-                SQLiteStatement stmt = null;
-                try {
-                    stmt = db.compileStatement("INSERT OR IGNORE INTO global(name,value)"
-                            + " VALUES(?,?);");
-                    loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED,
-                            ImsConfig.FeatureValueConstants.ON);
-                    db.setTransactionSuccessful();
-                } finally {
-                    db.endTransaction();
-                    if (stmt != null) stmt.close();
-                }
-            }
+            /*
+             * To control the default value by carrier config manager, initializing
+             * ENHANCED_4G_MODE_ENABLED has been removed.
+             */
             upgradeVersion = 116;
         }
 
@@ -2633,9 +2623,6 @@
 
             loadSetting(stmt, Settings.Global.DEVICE_NAME, getDefaultDeviceName());
 
-            loadSetting(stmt, Settings.Global.ENHANCED_4G_MODE_ENABLED,
-                    ImsConfig.FeatureValueConstants.ON);
-
             /*
              * IMPORTANT: Do not add any more upgrade steps here as the global,
              * secure, and system settings are no longer stored in a database
diff --git a/com/android/providers/settings/SettingsProtoDumpUtil.java b/com/android/providers/settings/SettingsProtoDumpUtil.java
index 67fb4d9..41b205b 100644
--- a/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -37,9 +37,11 @@
         // Global settings
         SettingsState globalSettings = settingsRegistry.getSettingsLocked(
                 SettingsProvider.SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
-        long globalSettingsToken = proto.start(SettingsServiceDumpProto.GLOBAL_SETTINGS);
-        dumpProtoGlobalSettingsLocked(globalSettings, proto);
-        proto.end(globalSettingsToken);
+        if (globalSettings != null) {
+            long globalSettingsToken = proto.start(SettingsServiceDumpProto.GLOBAL_SETTINGS);
+            dumpProtoGlobalSettingsLocked(globalSettings, proto);
+            proto.end(globalSettingsToken);
+        }
 
         // Per-user settings
         SparseBooleanArray users = settingsRegistry.getKnownUsersLocked();
@@ -67,19 +69,26 @@
 
         SettingsState secureSettings = settingsRegistry.getSettingsLocked(
                 SettingsProvider.SETTINGS_TYPE_SECURE, user.getIdentifier());
-        long secureSettingsToken = proto.start(UserSettingsProto.SECURE_SETTINGS);
-        dumpProtoSecureSettingsLocked(secureSettings, proto);
-        proto.end(secureSettingsToken);
+        if (secureSettings != null) {
+            long secureSettingsToken = proto.start(UserSettingsProto.SECURE_SETTINGS);
+            dumpProtoSecureSettingsLocked(secureSettings, proto);
+            proto.end(secureSettingsToken);
+        }
 
         SettingsState systemSettings = settingsRegistry.getSettingsLocked(
                 SettingsProvider.SETTINGS_TYPE_SYSTEM, user.getIdentifier());
-        long systemSettingsToken = proto.start(UserSettingsProto.SYSTEM_SETTINGS);
-        dumpProtoSystemSettingsLocked(systemSettings, proto);
-        proto.end(systemSettingsToken);
+        if (systemSettings != null) {
+            long systemSettingsToken = proto.start(UserSettingsProto.SYSTEM_SETTINGS);
+            dumpProtoSystemSettingsLocked(systemSettings, proto);
+            proto.end(systemSettingsToken);
+        }
     }
 
     private static void dumpProtoGlobalSettingsLocked(
             @NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+        s.dumpHistoricalOperations(p, GlobalSettingsProto.HISTORICAL_OPERATIONS);
+
+        // This uses the same order as in Settings.Global.
         dumpSetting(s, p,
                 Settings.Global.ADD_USERS_WHEN_LOCKED,
                 GlobalSettingsProto.ADD_USERS_WHEN_LOCKED);
@@ -114,6 +123,9 @@
                 Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
                 GlobalSettingsProto.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
         dumpSetting(s, p,
+                Settings.Global.BLUETOOTH_CLASS_OF_DEVICE,
+                GlobalSettingsProto.BLUETOOTH_CLASS_OF_DEVICE);
+        dumpSetting(s, p,
                 Settings.Global.BLUETOOTH_DISABLED_PROFILES,
                 GlobalSettingsProto.BLUETOOTH_DISABLED_PROFILES);
         dumpSetting(s, p,
@@ -194,6 +206,7 @@
         dumpSetting(s, p,
                 Settings.Global.CDMA_SUBSCRIPTION_MODE,
                 GlobalSettingsProto.CDMA_SUBSCRIPTION_MODE);
+        // Settings.Global.DEFAULT_RESTRICT_BACKGROUND_DATA intentionally excluded.
         dumpSetting(s, p,
                 Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
                 GlobalSettingsProto.DATA_ACTIVITY_TIMEOUT_MOBILE);
@@ -209,6 +222,10 @@
         dumpSetting(s, p,
                 Settings.Global.FORCE_ALLOW_ON_EXTERNAL,
                 GlobalSettingsProto.FORCE_ALLOW_ON_EXTERNAL);
+        // Settings.Global.DEFAULT_SM_DP_PLUS intentionally excluded.
+        dumpSetting(s, p,
+                Settings.Global.EUICC_PROVISIONED,
+                GlobalSettingsProto.EUICC_PROVISIONED);
         dumpSetting(s, p,
                 Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
                 GlobalSettingsProto.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
@@ -236,6 +253,7 @@
         dumpSetting(s, p,
                 Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
                 GlobalSettingsProto.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
+        // Settings.Global.INSTALL_NON_MARKET_APPS intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Global.HDMI_CONTROL_ENABLED,
                 GlobalSettingsProto.HDMI_CONTROL_ENABLED);
@@ -249,6 +267,21 @@
                 Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
                 GlobalSettingsProto.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED);
         dumpSetting(s, p,
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
+                GlobalSettingsProto.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS);
+        dumpSetting(s, p,
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
+                GlobalSettingsProto.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS);
+        dumpSetting(s, p,
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+                GlobalSettingsProto.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+        dumpSetting(s, p,
+                Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS,
+                GlobalSettingsProto.WIFI_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS);
+        dumpSetting(s, p,
+                Settings.Global.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+                GlobalSettingsProto.WIFI_SCAN_BACKGROUND_THROTTLE_PACKAGE_WHITELIST);
+        dumpSetting(s, p,
                 Settings.Global.MHL_INPUT_SWITCHING_ENABLED,
                 GlobalSettingsProto.MHL_INPUT_SWITCHING_ENABLED);
         dumpSetting(s, p,
@@ -279,6 +312,9 @@
                 Settings.Global.NETSTATS_SAMPLE_ENABLED,
                 GlobalSettingsProto.NETSTATS_SAMPLE_ENABLED);
         dumpSetting(s, p,
+                Settings.Global.NETSTATS_AUGMENT_ENABLED,
+                GlobalSettingsProto.NETSTATS_AUGMENT_ENABLED);
+        dumpSetting(s, p,
                 Settings.Global.NETSTATS_DEV_BUCKET_DURATION,
                 GlobalSettingsProto.NETSTATS_DEV_BUCKET_DURATION);
         dumpSetting(s, p,
@@ -420,6 +456,9 @@
                 Settings.Global.TETHER_DUN_APN,
                 GlobalSettingsProto.TETHER_DUN_APN);
         dumpSetting(s, p,
+                Settings.Global.TETHER_OFFLOAD_DISABLED,
+                GlobalSettingsProto.TETHER_OFFLOAD_DISABLED);
+        dumpSetting(s, p,
                 Settings.Global.CARRIER_APP_WHITELIST,
                 GlobalSettingsProto.CARRIER_APP_WHITELIST);
         dumpSetting(s, p,
@@ -450,6 +489,15 @@
                 Settings.Global.NETWORK_AVOID_BAD_WIFI,
                 GlobalSettingsProto.NETWORK_AVOID_BAD_WIFI);
         dumpSetting(s, p,
+                Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
+                GlobalSettingsProto.NETWORK_METERED_MULTIPATH_PREFERENCE);
+        dumpSetting(s, p,
+                Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
+                GlobalSettingsProto.NETWORK_WATCHLIST_LAST_REPORT_TIME);
+        dumpSetting(s, p,
+                Settings.Global.WIFI_BADGING_THRESHOLDS,
+                GlobalSettingsProto.WIFI_BADGING_THRESHOLDS);
+        dumpSetting(s, p,
                 Settings.Global.WIFI_DISPLAY_ON,
                 GlobalSettingsProto.WIFI_DISPLAY_ON);
         dumpSetting(s, p,
@@ -489,12 +537,30 @@
                 Settings.Global.WIFI_WAKEUP_ENABLED,
                 GlobalSettingsProto.WIFI_WAKEUP_ENABLED);
         dumpSetting(s, p,
+                Settings.Global.WIFI_WAKEUP_AVAILABLE,
+                GlobalSettingsProto.WIFI_WAKEUP_AVAILABLE);
+        dumpSetting(s, p,
+                Settings.Global.NETWORK_SCORING_UI_ENABLED,
+                GlobalSettingsProto.NETWORK_SCORING_UI_ENABLED);
+        dumpSetting(s, p,
+                Settings.Global.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS,
+                GlobalSettingsProto.SPEED_LABEL_CACHE_EVICTION_AGE_MILLIS);
+        dumpSetting(s, p,
                 Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
                 GlobalSettingsProto.NETWORK_RECOMMENDATIONS_ENABLED);
         dumpSetting(s, p,
                 Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
                 GlobalSettingsProto.NETWORK_RECOMMENDATIONS_PACKAGE);
         dumpSetting(s, p,
+                Settings.Global.USE_OPEN_WIFI_PACKAGE,
+                GlobalSettingsProto.USE_OPEN_WIFI_PACKAGE);
+        dumpSetting(s, p,
+                Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
+                GlobalSettingsProto.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS);
+        dumpSetting(s, p,
+                Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
+                GlobalSettingsProto.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS);
+        dumpSetting(s, p,
                 Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
                 GlobalSettingsProto.BLE_SCAN_ALWAYS_AVAILABLE);
         dumpSetting(s, p,
@@ -612,6 +678,12 @@
                 Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
                 GlobalSettingsProto.SYS_STORAGE_FULL_THRESHOLD_BYTES);
         dumpSetting(s, p,
+                Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE,
+                GlobalSettingsProto.SYS_STORAGE_CACHE_PERCENTAGE);
+        dumpSetting(s, p,
+                Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES,
+                GlobalSettingsProto.SYS_STORAGE_CACHE_MAX_BYTES);
+        dumpSetting(s, p,
                 Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
                 GlobalSettingsProto.SYNC_MAX_RETRY_DELAY_IN_SECONDS);
         dumpSetting(s, p,
@@ -627,6 +699,9 @@
                 Settings.Global.CAPTIVE_PORTAL_MODE,
                 GlobalSettingsProto.CAPTIVE_PORTAL_MODE);
         dumpSetting(s, p,
+                Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED,
+                GlobalSettingsProto.CAPTIVE_PORTAL_DETECTION_ENABLED);
+        dumpSetting(s, p,
                 Settings.Global.CAPTIVE_PORTAL_SERVER,
                 GlobalSettingsProto.CAPTIVE_PORTAL_SERVER);
         dumpSetting(s, p,
@@ -639,6 +714,9 @@
                 Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL,
                 GlobalSettingsProto.CAPTIVE_PORTAL_FALLBACK_URL);
         dumpSetting(s, p,
+                Settings.Global.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS,
+                GlobalSettingsProto.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS);
+        dumpSetting(s, p,
                 Settings.Global.CAPTIVE_PORTAL_USE_HTTPS,
                 GlobalSettingsProto.CAPTIVE_PORTAL_USE_HTTPS);
         dumpSetting(s, p,
@@ -684,6 +762,12 @@
                 Settings.Global.DEFAULT_DNS_SERVER,
                 GlobalSettingsProto.DEFAULT_DNS_SERVER);
         dumpSetting(s, p,
+                Settings.Global.PRIVATE_DNS_MODE,
+                GlobalSettingsProto.PRIVATE_DNS_MODE);
+        dumpSetting(s, p,
+                Settings.Global.PRIVATE_DNS_SPECIFIER,
+                GlobalSettingsProto.PRIVATE_DNS_SPECIFIER);
+        dumpSetting(s, p,
                 Settings.Global.BLUETOOTH_HEADSET_PRIORITY_PREFIX,
                 GlobalSettingsProto.BLUETOOTH_HEADSET_PRIORITY_PREFIX);
         dumpSetting(s, p,
@@ -717,12 +801,27 @@
                 Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
                 GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX);
         dumpSetting(s, p,
+                Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
+                GlobalSettingsProto.ACTIVITY_MANAGER_CONSTANTS);
+        dumpSetting(s, p,
                 Settings.Global.DEVICE_IDLE_CONSTANTS,
                 GlobalSettingsProto.DEVICE_IDLE_CONSTANTS);
         dumpSetting(s, p,
+                Settings.Global.BATTERY_SAVER_CONSTANTS,
+                GlobalSettingsProto.BATTERY_SAVER_CONSTANTS);
+        dumpSetting(s, p,
+                Settings.Global.ANOMALY_DETECTION_CONSTANTS,
+                GlobalSettingsProto.ANOMALY_DETECTION_CONSTANTS);
+        dumpSetting(s, p,
+                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS,
+                GlobalSettingsProto.ALWAYS_ON_DISPLAY_CONSTANTS);
+        dumpSetting(s, p,
                 Settings.Global.APP_IDLE_CONSTANTS,
                 GlobalSettingsProto.APP_IDLE_CONSTANTS);
         dumpSetting(s, p,
+                Settings.Global.POWER_MANAGER_CONSTANTS,
+                GlobalSettingsProto.POWER_MANAGER_CONSTANTS);
+        dumpSetting(s, p,
                 Settings.Global.ALARM_MANAGER_CONSTANTS,
                 GlobalSettingsProto.ALARM_MANAGER_CONSTANTS);
         dumpSetting(s, p,
@@ -732,6 +831,12 @@
                 Settings.Global.SHORTCUT_MANAGER_CONSTANTS,
                 GlobalSettingsProto.SHORTCUT_MANAGER_CONSTANTS);
         dumpSetting(s, p,
+                Settings.Global.DEVICE_POLICY_CONSTANTS,
+                GlobalSettingsProto.DEVICE_POLICY_CONSTANTS);
+        dumpSetting(s, p,
+                Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
+                GlobalSettingsProto.TEXT_CLASSIFIER_CONSTANTS);
+        dumpSetting(s, p,
                 Settings.Global.WINDOW_ANIMATION_SCALE,
                 GlobalSettingsProto.WINDOW_ANIMATION_SCALE);
         dumpSetting(s, p,
@@ -764,6 +869,7 @@
         dumpSetting(s, p,
                 Settings.Global.WAIT_FOR_DEBUGGER,
                 GlobalSettingsProto.WAIT_FOR_DEBUGGER);
+        // Settings.Global.SHOW_PROCESSES intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Global.LOW_POWER_MODE,
                 GlobalSettingsProto.LOW_POWER_MODE);
@@ -819,6 +925,18 @@
                 Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL,
                 GlobalSettingsProto.INTENT_FIREWALL_UPDATE_METADATA_URL);
         dumpSetting(s, p,
+                Settings.Global.LANG_ID_UPDATE_CONTENT_URL,
+                GlobalSettingsProto.LANG_ID_UPDATE_CONTENT_URL);
+        dumpSetting(s, p,
+                Settings.Global.LANG_ID_UPDATE_METADATA_URL,
+                GlobalSettingsProto.LANG_ID_UPDATE_METADATA_URL);
+        dumpSetting(s, p,
+                Settings.Global.SMART_SELECTION_UPDATE_CONTENT_URL,
+                GlobalSettingsProto.SMART_SELECTION_UPDATE_CONTENT_URL);
+        dumpSetting(s, p,
+                Settings.Global.SMART_SELECTION_UPDATE_METADATA_URL,
+                GlobalSettingsProto.SMART_SELECTION_UPDATE_METADATA_URL);
+        dumpSetting(s, p,
                 Settings.Global.SELINUX_STATUS,
                 GlobalSettingsProto.SELINUX_STATUS);
         dumpSetting(s, p,
@@ -882,11 +1000,8 @@
                 Settings.Global.ENABLE_EPHEMERAL_FEATURE,
                 GlobalSettingsProto.ENABLE_EPHEMERAL_FEATURE);
         dumpSetting(s, p,
-                Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
-                GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD);
-        dumpSetting(s, p,
-                Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
-                GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
+                Settings.Global.INSTANT_APP_DEXOPT_ENABLED,
+                GlobalSettingsProto.INSTANT_APP_DEXOPT_ENABLED);
         dumpSetting(s, p,
                 Settings.Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
                 GlobalSettingsProto.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD);
@@ -894,6 +1009,12 @@
                 Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
                 GlobalSettingsProto.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
         dumpSetting(s, p,
+                Settings.Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD,
+                GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD);
+        dumpSetting(s, p,
+                Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
+                GlobalSettingsProto.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
+        dumpSetting(s, p,
                 Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
                 GlobalSettingsProto.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD);
         dumpSetting(s, p,
@@ -909,12 +1030,30 @@
                 Settings.Global.DEVICE_DEMO_MODE,
                 GlobalSettingsProto.DEVICE_DEMO_MODE);
         dumpSetting(s, p,
+                Settings.Global.NETWORK_ACCESS_TIMEOUT_MS,
+                GlobalSettingsProto.NETWORK_ACCESS_TIMEOUT_MS);
+        dumpSetting(s, p,
                 Settings.Global.DATABASE_DOWNGRADE_REASON,
                 GlobalSettingsProto.DATABASE_DOWNGRADE_REASON);
         dumpSetting(s, p,
+                Settings.Global.DATABASE_CREATION_BUILDID,
+                GlobalSettingsProto.DATABASE_CREATION_BUILDID);
+        dumpSetting(s, p,
                 Settings.Global.CONTACTS_DATABASE_WAL_ENABLED,
                 GlobalSettingsProto.CONTACTS_DATABASE_WAL_ENABLED);
         dumpSetting(s, p,
+                Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
+                GlobalSettingsProto.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED);
+        dumpSetting(s, p,
+                Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED,
+                GlobalSettingsProto.BACKUP_REFACTORED_SERVICE_DISABLED);
+        dumpSetting(s, p,
+                Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
+                GlobalSettingsProto.EUICC_FACTORY_RESET_TIMEOUT_MILLIS);
+        dumpSetting(s, p,
+                Settings.Global.STORAGE_SETTINGS_CLOBBER_THRESHOLD,
+                GlobalSettingsProto.STORAGE_SETTINGS_CLOBBER_THRESHOLD);
+        dumpSetting(s, p,
                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
                 GlobalSettingsProto.MULTI_SIM_VOICE_CALL_SUBSCRIPTION);
         dumpSetting(s, p,
@@ -932,6 +1071,7 @@
         dumpSetting(s, p,
                 Settings.Global.NEW_CONTACT_AGGREGATOR,
                 GlobalSettingsProto.NEW_CONTACT_AGGREGATOR);
+        // Settings.Global.CONTACT_METADATA_SYNC intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Global.CONTACT_METADATA_SYNC_ENABLED,
                 GlobalSettingsProto.CONTACT_METADATA_SYNC_ENABLED);
@@ -942,8 +1082,31 @@
                 Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
                 GlobalSettingsProto.MAX_NOTIFICATION_ENQUEUE_RATE);
         dumpSetting(s, p,
+                Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS,
+                GlobalSettingsProto.SHOW_NOTIFICATION_CHANNEL_WARNINGS);
+        dumpSetting(s, p,
                 Settings.Global.CELL_ON,
                 GlobalSettingsProto.CELL_ON);
+        dumpSetting(s, p,
+                Settings.Global.SHOW_TEMPERATURE_WARNING,
+                GlobalSettingsProto.SHOW_TEMPERATURE_WARNING);
+        dumpSetting(s, p,
+                Settings.Global.WARNING_TEMPERATURE,
+                GlobalSettingsProto.WARNING_TEMPERATURE);
+        dumpSetting(s, p,
+                Settings.Global.ENABLE_DISKSTATS_LOGGING,
+                GlobalSettingsProto.ENABLE_DISKSTATS_LOGGING);
+        dumpSetting(s, p,
+                Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION,
+                GlobalSettingsProto.ENABLE_CACHE_QUOTA_CALCULATION);
+        dumpSetting(s, p,
+                Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE,
+                GlobalSettingsProto.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE);
+        // The list of snooze options for notifications. This is encoded as a key=value list,
+        // separated by commas.
+        dumpSetting(s, p,
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                GlobalSettingsProto.NOTIFICATION_SNOOZE_OPTIONS);
     }
 
     /** Dump a single {@link SettingsState.Setting} to a proto buf */
@@ -966,9 +1129,19 @@
 
     static void dumpProtoSecureSettingsLocked(
             @NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+        s.dumpHistoricalOperations(p, SecureSettingsProto.HISTORICAL_OPERATIONS);
+
+        // This uses the same order as in Settings.Secure.
+
+        // Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED intentionally excluded since it's deprecated.
+        // Settings.Secure.BUGREPORT_IN_POWER_MENU intentionally excluded since it's deprecated.
+        // Settings.Secure.ADB_ENABLED intentionally excluded since it's deprecated.
+        // Settings.Secure.ALLOW_MOCK_LOCATION intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.ANDROID_ID,
                 SecureSettingsProto.ANDROID_ID);
+        // Settings.Secure.BLUETOOTH_ON intentionally excluded since it's deprecated.
+        // Settings.Secure.DATA_ROAMING intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.DEFAULT_INPUT_METHOD,
                 SecureSettingsProto.DEFAULT_INPUT_METHOD);
@@ -987,9 +1160,16 @@
         dumpSetting(s, p,
                 Settings.Secure.AUTOFILL_SERVICE,
                 SecureSettingsProto.AUTOFILL_SERVICE);
+        // Settings.Secure.DEVICE_PROVISIONED intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.USER_SETUP_COMPLETE,
                 SecureSettingsProto.USER_SETUP_COMPLETE);
+        // Whether the current user has been set up via setup wizard (0 = false, 1 = true). This
+        // value differs from USER_SETUP_COMPLETE in that it can be reset back to 0 in case
+        // SetupWizard has been re-enabled on TV devices.
+        dumpSetting(s, p,
+                Settings.Secure.TV_USER_SETUP_COMPLETE,
+                SecureSettingsProto.TV_USER_SETUP_COMPLETE);
         dumpSetting(s, p,
                 Settings.Secure.COMPLETED_CATEGORY_PREFIX,
                 SecureSettingsProto.COMPLETED_CATEGORY_PREFIX);
@@ -1002,6 +1182,7 @@
         dumpSetting(s, p,
                 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
                 SecureSettingsProto.SHOW_IME_WITH_HARD_KEYBOARD);
+        // Settings.Secure.HTTP_PROXY intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.ALWAYS_ON_VPN_APP,
                 SecureSettingsProto.ALWAYS_ON_VPN_APP);
@@ -1012,17 +1193,33 @@
                 Settings.Secure.INSTALL_NON_MARKET_APPS,
                 SecureSettingsProto.INSTALL_NON_MARKET_APPS);
         dumpSetting(s, p,
+                Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED,
+                SecureSettingsProto.UNKNOWN_SOURCES_DEFAULT_REVERSED);
+        // Settings.Secure.LOCATION_PROVIDERS_ALLOWED intentionally excluded since it's deprecated.
+        dumpSetting(s, p,
                 Settings.Secure.LOCATION_MODE,
                 SecureSettingsProto.LOCATION_MODE);
         dumpSetting(s, p,
                 Settings.Secure.LOCATION_PREVIOUS_MODE,
                 SecureSettingsProto.LOCATION_PREVIOUS_MODE);
+        // Settings.Secure.LOCK_BIOMETRIC_WEAK_FLAGS intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
                 SecureSettingsProto.LOCK_TO_APP_EXIT_LOCKED);
+        // Settings.Secure.LOCK_PATTERN_ENABLED intentionally excluded since it's deprecated.
+        // Settings.Secure.LOCK_PATTERN_VISIBLE intentionally excluded since it's deprecated.
+        // Settings.Secure.LOCK_PATTERN_TACTICLE_FEEDBACK_ENABLED intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                 SecureSettingsProto.LOCK_SCREEN_LOCK_AFTER_TIMEOUT);
+        // Settings.Secure.LOCK_SCREEN_OWNER_INFO intentionally excluded since it's deprecated.
+        // Settings.Secure.LOCK_SCREEN_APPWIDGET_IDS intentionally excluded since it's deprecated.
+        // Settings.Secure.LOCK_SCREEN_FALLBACK_APPWIDGET_ID intentionally excluded since it's deprecated.
+        // Settings.Secure.LOCK_SCREEN_STICKY_APPWIDGET intentionally excluded since it's deprecated.
+        // Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED intentionally excluded since it's deprecated.
+        dumpSetting(s, p,
+                Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+                SecureSettingsProto.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
         dumpSetting(s, p,
                 Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
                 SecureSettingsProto.LOCK_SCREEN_ALLOW_REMOTE_INPUT);
@@ -1032,6 +1229,8 @@
         dumpSetting(s, p,
                 Settings.Secure.TRUST_AGENTS_INITIALIZED,
                 SecureSettingsProto.TRUST_AGENTS_INITIALIZED);
+        // Settings.Secure.LOGGING_ID intentionally excluded since it's deprecated.
+        // Settings.Secure.NETWORK_PREFERENCE intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.PARENTAL_CONTROL_ENABLED,
                 SecureSettingsProto.PARENTAL_CONTROL_ENABLED);
@@ -1044,10 +1243,27 @@
         dumpSetting(s, p,
                 Settings.Secure.SETTINGS_CLASSNAME,
                 SecureSettingsProto.SETTINGS_CLASSNAME);
+        // Settings.Secure.USB_MASS_STORAGE_ENABLED intentionally excluded since it's deprecated.
+        // Settings.Secure.USE_GOOGLE_MAIL intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_ENABLED,
                 SecureSettingsProto.ACCESSIBILITY_ENABLED);
         dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED,
+                SecureSettingsProto.ACCESSIBILITY_SHORTCUT_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
+                SecureSettingsProto.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN);
+        dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                SecureSettingsProto.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN);
+        dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+                SecureSettingsProto.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
+        dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+                SecureSettingsProto.ACCESSIBILITY_BUTTON_TARGET_COMPONENT);
+        dumpSetting(s, p,
                 Settings.Secure.TOUCH_EXPLORATION_ENABLED,
                 SecureSettingsProto.TOUCH_EXPLORATION_ENABLED);
         dumpSetting(s, p,
@@ -1066,9 +1282,15 @@
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
                 SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
         dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+                SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+        dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
                 SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE);
         dumpSetting(s, p,
+                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+                SecureSettingsProto.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE);
+        dumpSetting(s, p,
                 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
                 SecureSettingsProto.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
         dumpSetting(s, p,
@@ -1134,6 +1356,7 @@
         dumpSetting(s, p,
                 Settings.Secure.DISPLAY_DENSITY_FORCED,
                 SecureSettingsProto.DISPLAY_DENSITY_FORCED);
+        // Settings.Secure.TTS_USE_DEFAULTS intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.TTS_DEFAULT_RATE,
                 SecureSettingsProto.TTS_DEFAULT_RATE);
@@ -1143,15 +1366,37 @@
         dumpSetting(s, p,
                 Settings.Secure.TTS_DEFAULT_SYNTH,
                 SecureSettingsProto.TTS_DEFAULT_SYNTH);
+        // Settings.Secure.TTS_DEFAULT_LANG intentionally excluded since it's deprecated.
+        // Settings.Secure.TTS_DEFAULT_COUNTRY intentionally excluded since it's deprecated.
+        // Settings.Secure.TTS_DEFAULT_VARIANT intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.TTS_DEFAULT_LOCALE,
                 SecureSettingsProto.TTS_DEFAULT_LOCALE);
         dumpSetting(s, p,
                 Settings.Secure.TTS_ENABLED_PLUGINS,
                 SecureSettingsProto.TTS_ENABLED_PLUGINS);
+        // Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_NUM_OPEN_NETWORKS_KEPT intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_ON intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_AP_COUNT intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_ON intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_WATCH_LIST intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_PING_COUNT intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT intentionally excluded since it's deprecated.
+        // Settings.Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                 SecureSettingsProto.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS);
+        // Settings.Secure.BACKGROUND_DATA intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
                 SecureSettingsProto.ALLOWED_GEOLOCATION_ORIGINS);
@@ -1179,6 +1424,7 @@
         dumpSetting(s, p,
                 Settings.Secure.LAST_SETUP_SHOWN,
                 SecureSettingsProto.LAST_SETUP_SHOWN);
+        // Settings.Secure.WIFI_IDLE_MS intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
                 SecureSettingsProto.SEARCH_GLOBAL_SEARCH_ACTIVITY);
@@ -1285,6 +1531,9 @@
                 Settings.Secure.DOZE_PULSE_ON_PICK_UP,
                 SecureSettingsProto.DOZE_PULSE_ON_PICK_UP);
         dumpSetting(s, p,
+                Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
+                SecureSettingsProto.DOZE_PULSE_ON_LONG_PRESS);
+        dumpSetting(s, p,
                 Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
                 SecureSettingsProto.DOZE_PULSE_ON_DOUBLE_TAP);
         dumpSetting(s, p,
@@ -1351,6 +1600,9 @@
                 Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
                 SecureSettingsProto.PAYMENT_SERVICE_SEARCH_URI);
         dumpSetting(s, p,
+                Settings.Secure.AUTOFILL_SERVICE_SEARCH_URI,
+                SecureSettingsProto.AUTOFILL_SERVICE_SEARCH_URI);
+        dumpSetting(s, p,
                 Settings.Secure.SKIP_FIRST_USE_HINTS,
                 SecureSettingsProto.SKIP_FIRST_USE_HINTS);
         dumpSetting(s, p,
@@ -1387,18 +1639,42 @@
                 Settings.Secure.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED,
                 SecureSettingsProto.CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED);
         dumpSetting(s, p,
+                Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED,
+                SecureSettingsProto.CAMERA_LIFT_TRIGGER_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.ASSIST_GESTURE_ENABLED,
+                SecureSettingsProto.ASSIST_GESTURE_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.ASSIST_GESTURE_SENSITIVITY,
+                SecureSettingsProto.ASSIST_GESTURE_SENSITIVITY);
+        dumpSetting(s, p,
+                Settings.Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
+                SecureSettingsProto.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.ASSIST_GESTURE_WAKE_ENABLED,
+                SecureSettingsProto.ASSIST_GESTURE_WAKE_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE,
+                SecureSettingsProto.ASSIST_GESTURE_SETUP_COMPLETE);
+        dumpSetting(s, p,
                 Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
                 SecureSettingsProto.NIGHT_DISPLAY_ACTIVATED);
         dumpSetting(s, p,
                 Settings.Secure.NIGHT_DISPLAY_AUTO_MODE,
                 SecureSettingsProto.NIGHT_DISPLAY_AUTO_MODE);
         dumpSetting(s, p,
+                Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
+                SecureSettingsProto.NIGHT_DISPLAY_COLOR_TEMPERATURE);
+        dumpSetting(s, p,
                 Settings.Secure.NIGHT_DISPLAY_CUSTOM_START_TIME,
                 SecureSettingsProto.NIGHT_DISPLAY_CUSTOM_START_TIME);
         dumpSetting(s, p,
                 Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME,
                 SecureSettingsProto.NIGHT_DISPLAY_CUSTOM_END_TIME);
         dumpSetting(s, p,
+                Settings.Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                SecureSettingsProto.NIGHT_DISPLAY_LAST_ACTIVATED_TIME);
+        dumpSetting(s, p,
                 Settings.Secure.ENABLED_VR_LISTENERS,
                 SecureSettingsProto.ENABLED_VR_LISTENERS);
         dumpSetting(s, p,
@@ -1423,6 +1699,9 @@
                 Settings.Secure.AUTOMATIC_STORAGE_MANAGER_LAST_RUN,
                 SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_LAST_RUN);
         dumpSetting(s, p,
+                Settings.Secure.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY,
+                SecureSettingsProto.AUTOMATIC_STORAGE_MANAGER_TURNED_OFF_BY_POLICY);
+        dumpSetting(s, p,
                 Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
                 SecureSettingsProto.SYSTEM_NAVIGATION_KEYS_ENABLED);
         dumpSetting(s, p,
@@ -1438,33 +1717,76 @@
                 Settings.Secure.DEVICE_PAIRED,
                 SecureSettingsProto.DEVICE_PAIRED);
         dumpSetting(s, p,
+                Settings.Secure.PACKAGE_VERIFIER_STATE,
+                SecureSettingsProto.PACKAGE_VERIFIER_STATE);
+        dumpSetting(s, p,
+                Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
+                SecureSettingsProto.CMAS_ADDITIONAL_BROADCAST_PKG);
+        dumpSetting(s, p,
                 Settings.Secure.NOTIFICATION_BADGING,
                 SecureSettingsProto.NOTIFICATION_BADGING);
         dumpSetting(s, p,
+                Settings.Secure.QS_AUTO_ADDED_TILES,
+                SecureSettingsProto.QS_AUTO_ADDED_TILES);
+        dumpSetting(s, p,
+                Settings.Secure.LOCKDOWN_IN_POWER_MENU,
+                SecureSettingsProto.LOCKDOWN_IN_POWER_MENU);
+        dumpSetting(s, p,
                 Settings.Secure.BACKUP_MANAGER_CONSTANTS,
                 SecureSettingsProto.BACKUP_MANAGER_CONSTANTS);
     }
 
     private static void dumpProtoSystemSettingsLocked(
             @NonNull SettingsState s, @NonNull ProtoOutputStream p) {
+        s.dumpHistoricalOperations(p, SystemSettingsProto.HISTORICAL_OPERATIONS);
+
+        // This uses the same order as in Settings.System.
+
+        // Settings.System.STAY_ON_WHILE_PLUGGED_IN intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.END_BUTTON_BEHAVIOR,
                 SystemSettingsProto.END_BUTTON_BEHAVIOR);
         dumpSetting(s, p,
                 Settings.System.ADVANCED_SETTINGS,
                 SystemSettingsProto.ADVANCED_SETTINGS);
+        // Settings.System.AIRPLANE_MODE_ON intentionally excluded since it's deprecated.
+        // Settings.System.RADIO_BLUETOOTH intentionally excluded since it's deprecated.
+        // Settings.System.RADIO_WIFI intentionally excluded since it's deprecated.
+        // Settings.System.RADIO_WIMAX intentionally excluded since it's deprecated.
+        // Settings.System.RADIO_CELL intentionally excluded since it's deprecated.
+        // Settings.System.RADIO_NFC intentionally excluded since it's deprecated.
+        // Settings.System.AIRPLANE_MODE_RADIOS intentionally excluded since it's deprecated.
+        // Settings.System.AIRPLANE_MODE_TOGGLABLE_RADIOS intentionally excluded since it's deprecated.
+        // Settings.System.WIFI_SLEEP_POLICY intentionally excluded since it's deprecated.
+        // Settings.System.MODE_RINGER intentionally excluded since it's deprecated.
+        // Settings.System.WIFI_USE_STATIC_IP intentionally excluded since it's deprecated.
+        // Settings.System.WIFI_STATIC_IP intentionally excluded since it's deprecated.
+        // Settings.System.WIFI_STATIC_GATEWAY intentionally excluded since it's deprecated.
+        // Settings.System.WIFI_STATIC_NETMASK intentionally excluded since it's deprecated.
+        // Settings.System.WIFI_STATIC_DNS1 intentionally excluded since it's deprecated.
+        // Settings.System.WIFI_STATIC_DNS2 intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.BLUETOOTH_DISCOVERABILITY,
                 SystemSettingsProto.BLUETOOTH_DISCOVERABILITY);
         dumpSetting(s, p,
                 Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
                 SystemSettingsProto.BLUETOOTH_DISCOVERABILITY_TIMEOUT);
+        // Settings.System.LOCK_PATTERN_ENABLED intentionally excluded since it's deprecated.
+        // Settings.System.LOCK_PATTERN_VISIBLE intentionally excluded since it's deprecated.
+        // Settings.System.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED intentionally excluded since it's deprecated.
+        // Settings.System.NEXT_ALARM_FORMATTED intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.FONT_SCALE,
                 SystemSettingsProto.FONT_SCALE);
         dumpSetting(s, p,
                 Settings.System.SYSTEM_LOCALES,
                 SystemSettingsProto.SYSTEM_LOCALES);
+        // Settings.System.DEBUG_APP intentionally excluded since it's deprecated.
+        // Settings.System.WAIT_FOR_DEBUGGER intentionally excluded since it's deprecated.
+        // Settings.System.DIM_SCREEN intentionally excluded since it's deprecated.
+        dumpSetting(s, p,
+                Settings.System.DISPLAY_COLOR_MODE,
+                SystemSettingsProto.DISPLAY_COLOR_MODE);
         dumpSetting(s, p,
                 Settings.System.SCREEN_OFF_TIMEOUT,
                 SystemSettingsProto.SCREEN_OFF_TIMEOUT);
@@ -1480,6 +1802,8 @@
         dumpSetting(s, p,
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                 SystemSettingsProto.SCREEN_AUTO_BRIGHTNESS_ADJ);
+        // Settings.System.SHOW_PROCESSES intentionally excluded since it's deprecated.
+        // Settings.System.ALWAYS_FINISH_ACTIVITIES intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.MODE_RINGER_STREAMS_AFFECTED,
                 SystemSettingsProto.MODE_RINGER_STREAMS_AFFECTED);
@@ -1514,11 +1838,15 @@
                 Settings.System.VOLUME_BLUETOOTH_SCO,
                 SystemSettingsProto.VOLUME_BLUETOOTH_SCO);
         dumpSetting(s, p,
+                Settings.System.VOLUME_ACCESSIBILITY,
+                SystemSettingsProto.VOLUME_ACCESSIBILITY);
+        dumpSetting(s, p,
                 Settings.System.VOLUME_MASTER,
                 SystemSettingsProto.VOLUME_MASTER);
         dumpSetting(s, p,
                 Settings.System.MASTER_MONO,
                 SystemSettingsProto.MASTER_MONO);
+        // Settings.System.NOTIFICATIONS_USE_RING_VOLUME intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.VIBRATE_IN_SILENT,
                 SystemSettingsProto.VIBRATE_IN_SILENT);
@@ -1561,6 +1889,9 @@
         dumpSetting(s, p,
                 Settings.System.SHOW_GTALK_SERVICE_STATUS,
                 SystemSettingsProto.SHOW_GTALK_SERVICE_STATUS);
+        // Settings.System.WALLPAPER_ACTIVITY intentionally excluded since it's deprecated.
+        // Settings.System.AUTO_TIME intentionally excluded since it's deprecated.
+        // Settings.System.AUTO_TIME_ZONE intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.TIME_12_24,
                 SystemSettingsProto.TIME_12_24);
@@ -1570,6 +1901,9 @@
         dumpSetting(s, p,
                 Settings.System.SETUP_WIZARD_HAS_RUN,
                 SystemSettingsProto.SETUP_WIZARD_HAS_RUN);
+        // Settings.System.WINDOW_ANIMATION_SCALE intentionally excluded since it's deprecated.
+        // Settings.System.TRANSITION_ANIMATION_SCALE intentionally excluded since it's deprecated.
+        // Settings.System.ANIMATOR_ANIMATION_SCALE intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.ACCELEROMETER_ROTATION,
                 SystemSettingsProto.ACCELEROMETER_ROTATION);
@@ -1600,6 +1934,7 @@
         dumpSetting(s, p,
                 Settings.System.HAPTIC_FEEDBACK_ENABLED,
                 SystemSettingsProto.HAPTIC_FEEDBACK_ENABLED);
+        // Settings.System.SHOW_WEB_SUGGESTIONS intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.NOTIFICATION_LIGHT_PULSE,
                 SystemSettingsProto.NOTIFICATION_LIGHT_PULSE);
@@ -1612,12 +1947,21 @@
         dumpSetting(s, p,
                 Settings.System.WINDOW_ORIENTATION_LISTENER_LOG,
                 SystemSettingsProto.WINDOW_ORIENTATION_LISTENER_LOG);
+        // Settings.System.POWER_SOUNDS_ENABLED intentionally excluded since it's deprecated.
+        // Settings.System.DOCK_SOUNDS_ENABLED intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
                 SystemSettingsProto.LOCKSCREEN_SOUNDS_ENABLED);
         dumpSetting(s, p,
                 Settings.System.LOCKSCREEN_DISABLED,
                 SystemSettingsProto.LOCKSCREEN_DISABLED);
+        // Settings.System.LOW_BATTERY_SOUND intentionally excluded since it's deprecated.
+        // Settings.System.DESK_DOCK_SOUND intentionally excluded since it's deprecated.
+        // Settings.System.DESK_UNDOCK_SOUND intentionally excluded since it's deprecated.
+        // Settings.System.CAR_DOCK_SOUND intentionally excluded since it's deprecated.
+        // Settings.System.CAR_UNDOCK_SOUND intentionally excluded since it's deprecated.
+        // Settings.System.LOCK_SOUND intentionally excluded since it's deprecated.
+        // Settings.System.UNLOCK_SOUND intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.SIP_RECEIVE_CALLS,
                 SystemSettingsProto.SIP_RECEIVE_CALLS);
@@ -1630,6 +1974,7 @@
         dumpSetting(s, p,
                 Settings.System.SIP_ADDRESS_ONLY,
                 SystemSettingsProto.SIP_ADDRESS_ONLY);
+        // Settings.System.SIP_ASK_ME_EACH_TIME intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.System.POINTER_SPEED,
                 SystemSettingsProto.POINTER_SPEED);
@@ -1640,7 +1985,12 @@
                 Settings.System.EGG_MODE,
                 SystemSettingsProto.EGG_MODE);
         dumpSetting(s, p,
+                Settings.System.SHOW_BATTERY_PERCENT,
+                SystemSettingsProto.SHOW_BATTERY_PERCENT);
+        dumpSetting(s, p,
                 Settings.System.WHEN_TO_MAKE_WIFI_CALLS,
                 SystemSettingsProto.WHEN_TO_MAKE_WIFI_CALLS);
+        // The rest of the settings were moved to Settings.Secure, and are thus excluded here since
+        // they're deprecated from Settings.System.
     }
 }
diff --git a/com/android/providers/settings/SettingsProvider.java b/com/android/providers/settings/SettingsProvider.java
index 36f9b84..258c96c 100644
--- a/com/android/providers/settings/SettingsProvider.java
+++ b/com/android/providers/settings/SettingsProvider.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.backup.BackupManager;
@@ -657,7 +658,6 @@
 
         synchronized (mLock) {
             SettingsProtoDumpUtil.dumpProtoLocked(mSettingsRegistry, proto);
-
         }
 
         proto.flush();
@@ -2284,6 +2284,7 @@
             return users;
         }
 
+        @Nullable
         public SettingsState getSettingsLocked(int type, int userId) {
             final int key = makeKey(type, userId);
             return peekSettingsStateLocked(key);
@@ -2578,6 +2579,7 @@
             ssaidSettings.deleteSettingLocked(Integer.toString(uid));
         }
 
+        @Nullable
         private SettingsState peekSettingsStateLocked(int key) {
             SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
diff --git a/com/android/providers/settings/SettingsState.java b/com/android/providers/settings/SettingsState.java
index 4151ada..f901bca 100644
--- a/com/android/providers/settings/SettingsState.java
+++ b/com/android/providers/settings/SettingsState.java
@@ -434,8 +434,9 @@
      * Dump historical operations as a proto buf.
      *
      * @param proto The proto buf stream to dump to
+     * @param fieldId The repeated field ID to use to save an operation to.
      */
-    void dumpProtoHistoricalOperations(@NonNull ProtoOutputStream proto) {
+    void dumpHistoricalOperations(@NonNull ProtoOutputStream proto, long fieldId) {
         synchronized (mLock) {
             if (mHistoricalOperations == null) {
                 return;
@@ -448,7 +449,8 @@
                     index = operationCount + index;
                 }
                 HistoricalOperation operation = mHistoricalOperations.get(index);
-                long settingsOperationToken = proto.start(GlobalSettingsProto.HISTORICAL_OP);
+
+                final long token = proto.start(fieldId);
                 proto.write(SettingsOperationProto.TIMESTAMP, operation.mTimestamp);
                 proto.write(SettingsOperationProto.OPERATION, operation.mOperation);
                 if (operation.mSetting != null) {
@@ -457,7 +459,7 @@
                     // add is what the current data is).
                     proto.write(SettingsOperationProto.SETTING, operation.mSetting.getName());
                 }
-                proto.end(settingsOperationToken);
+                proto.end(token);
             }
         }
     }
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index 68546bd..ea0ed27 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -38,7 +38,7 @@
 import android.content.pm.PackageManager;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
-import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V1_0.HealthInfo;
 import android.hardware.health.V2_0.IHealthInfoCallback;
 import android.hardware.health.V2_0.IHealth;
 import android.hardware.health.V2_0.Result;
@@ -49,6 +49,7 @@
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBatteryPropertiesListener;
 import android.os.IBatteryPropertiesRegistrar;
 import android.os.IBinder;
@@ -56,6 +57,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UEventObserver;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -74,6 +76,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -175,6 +178,7 @@
     private HealthServiceWrapper mHealthServiceWrapper;
     private HealthHalCallback mHealthHalCallback;
     private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+    private HandlerThread mHandlerThread;
 
     public BatteryService(Context context) {
         super(context);
@@ -246,6 +250,7 @@
     }
 
     private void registerHealthCallback() {
+        traceBegin("HealthInitWrapper");
         mHealthServiceWrapper = new HealthServiceWrapper();
         mHealthHalCallback = new HealthHalCallback();
         // IHealth is lazily retrieved.
@@ -259,8 +264,11 @@
         } catch (NoSuchElementException ex) {
             Slog.e(TAG, "health: cannot register callback. (no supported health HAL service)");
             throw ex;
+        } finally {
+            traceEnd();
         }
 
+        traceBegin("HealthInitWaitUpdate");
         // init register for new service notifications, and IServiceManager should return the
         // existing service in a near future. Wait for this.update() to instantiate
         // the initial mHealthInfo.
@@ -280,6 +288,7 @@
 
         Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait)
                 + "ms and received the update.");
+        traceEnd();
     }
 
     private void updateBatteryWarningLevelLocked() {
@@ -302,16 +311,16 @@
     private boolean isPoweredLocked(int plugTypeSet) {
         // assume we are powered if battery state is unknown so
         // the "stay on while plugged in" option will work.
-        if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+        if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.legacy.chargerAcOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.chargerAcOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.legacy.chargerUsbOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.chargerUsbOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.legacy.chargerWirelessOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.chargerWirelessOnline) {
             return true;
         }
         return false;
@@ -328,15 +337,15 @@
          *   (becomes <= mLowBatteryWarningLevel).
          */
         return !plugged
-                && mHealthInfo.legacy.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
-                && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel
+                && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+                && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
                 && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
     }
 
     private void shutdownIfNoPowerLocked() {
         // shut down gracefully if our battery is critically low and we are not powered.
         // wait until the system has booted before attempting to display the shutdown dialog.
-        if (mHealthInfo.legacy.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+        if (mHealthInfo.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -357,7 +366,7 @@
         // shut down gracefully if temperature is too high (> 68.0C by default)
         // wait until the system has booted before attempting to display the
         // shutdown dialog.
-        if (mHealthInfo.legacy.batteryTemperature > mShutdownBatteryTemperature) {
+        if (mHealthInfo.batteryTemperature > mShutdownBatteryTemperature) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
@@ -375,6 +384,7 @@
     }
 
     private void update(HealthInfo info) {
+        traceBegin("HealthInfoUpdate");
         synchronized (mLock) {
             if (!mUpdatesStopped) {
                 mHealthInfo = info;
@@ -385,40 +395,38 @@
                 copy(mLastHealthInfo, info);
             }
         }
+        traceEnd();
     }
 
     private static void copy(HealthInfo dst, HealthInfo src) {
-        dst.legacy.chargerAcOnline = src.legacy.chargerAcOnline;
-        dst.legacy.chargerUsbOnline = src.legacy.chargerUsbOnline;
-        dst.legacy.chargerWirelessOnline = src.legacy.chargerWirelessOnline;
-        dst.legacy.maxChargingCurrent = src.legacy.maxChargingCurrent;
-        dst.legacy.maxChargingVoltage = src.legacy.maxChargingVoltage;
-        dst.legacy.batteryStatus = src.legacy.batteryStatus;
-        dst.legacy.batteryHealth = src.legacy.batteryHealth;
-        dst.legacy.batteryPresent = src.legacy.batteryPresent;
-        dst.legacy.batteryLevel = src.legacy.batteryLevel;
-        dst.legacy.batteryVoltage = src.legacy.batteryVoltage;
-        dst.legacy.batteryTemperature = src.legacy.batteryTemperature;
-        dst.legacy.batteryCurrent = src.legacy.batteryCurrent;
-        dst.legacy.batteryCycleCount = src.legacy.batteryCycleCount;
-        dst.legacy.batteryFullCharge = src.legacy.batteryFullCharge;
-        dst.legacy.batteryChargeCounter = src.legacy.batteryChargeCounter;
-        dst.legacy.batteryTechnology = src.legacy.batteryTechnology;
-        dst.batteryCurrentAverage = src.batteryCurrentAverage;
-        dst.batteryCapacity = src.batteryCapacity;
-        dst.energyCounter = src.energyCounter;
+        dst.chargerAcOnline = src.chargerAcOnline;
+        dst.chargerUsbOnline = src.chargerUsbOnline;
+        dst.chargerWirelessOnline = src.chargerWirelessOnline;
+        dst.maxChargingCurrent = src.maxChargingCurrent;
+        dst.maxChargingVoltage = src.maxChargingVoltage;
+        dst.batteryStatus = src.batteryStatus;
+        dst.batteryHealth = src.batteryHealth;
+        dst.batteryPresent = src.batteryPresent;
+        dst.batteryLevel = src.batteryLevel;
+        dst.batteryVoltage = src.batteryVoltage;
+        dst.batteryTemperature = src.batteryTemperature;
+        dst.batteryCurrent = src.batteryCurrent;
+        dst.batteryCycleCount = src.batteryCycleCount;
+        dst.batteryFullCharge = src.batteryFullCharge;
+        dst.batteryChargeCounter = src.batteryChargeCounter;
+        dst.batteryTechnology = src.batteryTechnology;
     }
 
     private void processValuesLocked(boolean force) {
         boolean logOutlier = false;
         long dischargeDuration = 0;
 
-        mBatteryLevelCritical = (mHealthInfo.legacy.batteryLevel <= mCriticalBatteryLevel);
-        if (mHealthInfo.legacy.chargerAcOnline) {
+        mBatteryLevelCritical = (mHealthInfo.batteryLevel <= mCriticalBatteryLevel);
+        if (mHealthInfo.chargerAcOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
-        } else if (mHealthInfo.legacy.chargerUsbOnline) {
+        } else if (mHealthInfo.chargerUsbOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
-        } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+        } else if (mHealthInfo.chargerWirelessOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
         } else {
             mPlugType = BATTERY_PLUGGED_NONE;
@@ -433,10 +441,10 @@
 
         // Let the battery stats keep track of the current level.
         try {
-            mBatteryStats.setBatteryState(mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth,
-                    mPlugType, mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryTemperature,
-                    mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryChargeCounter,
-                    mHealthInfo.legacy.batteryFullCharge);
+            mBatteryStats.setBatteryState(mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
+                    mPlugType, mHealthInfo.batteryLevel, mHealthInfo.batteryTemperature,
+                    mHealthInfo.batteryVoltage, mHealthInfo.batteryChargeCounter,
+                    mHealthInfo.batteryFullCharge);
         } catch (RemoteException e) {
             // Should never happen.
         }
@@ -444,16 +452,16 @@
         shutdownIfNoPowerLocked();
         shutdownIfOverTempLocked();
 
-        if (force || (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
-                mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
-                mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
-                mHealthInfo.legacy.batteryLevel != mLastBatteryLevel ||
+        if (force || (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+                mHealthInfo.batteryHealth != mLastBatteryHealth ||
+                mHealthInfo.batteryPresent != mLastBatteryPresent ||
+                mHealthInfo.batteryLevel != mLastBatteryLevel ||
                 mPlugType != mLastPlugType ||
-                mHealthInfo.legacy.batteryVoltage != mLastBatteryVoltage ||
-                mHealthInfo.legacy.batteryTemperature != mLastBatteryTemperature ||
-                mHealthInfo.legacy.maxChargingCurrent != mLastMaxChargingCurrent ||
-                mHealthInfo.legacy.maxChargingVoltage != mLastMaxChargingVoltage ||
-                mHealthInfo.legacy.batteryChargeCounter != mLastChargeCounter ||
+                mHealthInfo.batteryVoltage != mLastBatteryVoltage ||
+                mHealthInfo.batteryTemperature != mLastBatteryTemperature ||
+                mHealthInfo.maxChargingCurrent != mLastMaxChargingCurrent ||
+                mHealthInfo.maxChargingVoltage != mLastMaxChargingVoltage ||
+                mHealthInfo.batteryChargeCounter != mLastChargeCounter ||
                 mInvalidCharger != mLastInvalidCharger)) {
 
             if (mPlugType != mLastPlugType) {
@@ -462,33 +470,33 @@
 
                     // There's no value in this data unless we've discharged at least once and the
                     // battery level has changed; so don't log until it does.
-                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.legacy.batteryLevel) {
+                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) {
                         dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
                         logOutlier = true;
                         EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
-                                mDischargeStartLevel, mHealthInfo.legacy.batteryLevel);
+                                mDischargeStartLevel, mHealthInfo.batteryLevel);
                         // make sure we see a discharge event before logging again
                         mDischargeStartTime = 0;
                     }
                 } else if (mPlugType == BATTERY_PLUGGED_NONE) {
                     // charging -> discharging or we just powered up
                     mDischargeStartTime = SystemClock.elapsedRealtime();
-                    mDischargeStartLevel = mHealthInfo.legacy.batteryLevel;
+                    mDischargeStartLevel = mHealthInfo.batteryLevel;
                 }
             }
-            if (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
-                    mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
-                    mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
+            if (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+                    mHealthInfo.batteryHealth != mLastBatteryHealth ||
+                    mHealthInfo.batteryPresent != mLastBatteryPresent ||
                     mPlugType != mLastPlugType) {
                 EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
-                        mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, mHealthInfo.legacy.batteryPresent ? 1 : 0,
-                        mPlugType, mHealthInfo.legacy.batteryTechnology);
+                        mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
+                        mPlugType, mHealthInfo.batteryTechnology);
             }
-            if (mHealthInfo.legacy.batteryLevel != mLastBatteryLevel) {
+            if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
                 // Don't do this just from voltage or temperature changes, that is
                 // too noisy.
                 EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
-                        mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryTemperature);
+                        mHealthInfo.batteryLevel, mHealthInfo.batteryVoltage, mHealthInfo.batteryTemperature);
             }
             if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
                     mPlugType == BATTERY_PLUGGED_NONE) {
@@ -501,16 +509,16 @@
             if (!mBatteryLevelLow) {
                 // Should we now switch in to low battery mode?
                 if (mPlugType == BATTERY_PLUGGED_NONE
-                        && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel) {
+                        && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
                     mBatteryLevelLow = true;
                 }
             } else {
                 // Should we now switch out of low battery mode?
                 if (mPlugType != BATTERY_PLUGGED_NONE) {
                     mBatteryLevelLow = false;
-                } else if (mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel)  {
+                } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel)  {
                     mBatteryLevelLow = false;
-                } else if (force && mHealthInfo.legacy.batteryLevel >= mLowBatteryWarningLevel) {
+                } else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
                     // If being forced, the previous state doesn't matter, we will just
                     // absolutely check to see if we are now above the warning level.
                     mBatteryLevelLow = false;
@@ -557,7 +565,7 @@
                     }
                 });
             } else if (mSentLowBatteryBroadcast &&
-                    mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
+                    mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
                 mSentLowBatteryBroadcast = false;
                 final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -583,16 +591,16 @@
                 logOutlierLocked(dischargeDuration);
             }
 
-            mLastBatteryStatus = mHealthInfo.legacy.batteryStatus;
-            mLastBatteryHealth = mHealthInfo.legacy.batteryHealth;
-            mLastBatteryPresent = mHealthInfo.legacy.batteryPresent;
-            mLastBatteryLevel = mHealthInfo.legacy.batteryLevel;
+            mLastBatteryStatus = mHealthInfo.batteryStatus;
+            mLastBatteryHealth = mHealthInfo.batteryHealth;
+            mLastBatteryPresent = mHealthInfo.batteryPresent;
+            mLastBatteryLevel = mHealthInfo.batteryLevel;
             mLastPlugType = mPlugType;
-            mLastBatteryVoltage = mHealthInfo.legacy.batteryVoltage;
-            mLastBatteryTemperature = mHealthInfo.legacy.batteryTemperature;
-            mLastMaxChargingCurrent = mHealthInfo.legacy.maxChargingCurrent;
-            mLastMaxChargingVoltage = mHealthInfo.legacy.maxChargingVoltage;
-            mLastChargeCounter = mHealthInfo.legacy.batteryChargeCounter;
+            mLastBatteryVoltage = mHealthInfo.batteryVoltage;
+            mLastBatteryTemperature = mHealthInfo.batteryTemperature;
+            mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrent;
+            mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltage;
+            mLastChargeCounter = mHealthInfo.batteryChargeCounter;
             mLastBatteryLevelCritical = mBatteryLevelCritical;
             mLastInvalidCharger = mInvalidCharger;
         }
@@ -604,23 +612,23 @@
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                 | Intent.FLAG_RECEIVER_REPLACE_PENDING);
 
-        int icon = getIconLocked(mHealthInfo.legacy.batteryLevel);
+        int icon = getIconLocked(mHealthInfo.batteryLevel);
 
         intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
-        intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.legacy.batteryStatus);
-        intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.legacy.batteryHealth);
-        intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.legacy.batteryPresent);
-        intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.legacy.batteryLevel);
+        intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.batteryStatus);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.batteryHealth);
+        intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.batteryPresent);
+        intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.batteryLevel);
         intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
         intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
-        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.legacy.batteryVoltage);
-        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
-        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltage);
+        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperature);
+        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.batteryTechnology);
         intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
-        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
-        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
-        intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
+        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+        intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
         if (DEBUG) {
             Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
                     + ", info:" + mHealthInfo.toString());
@@ -684,14 +692,14 @@
                 long durationThreshold = Long.parseLong(durationThresholdString);
                 int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
                 if (duration <= durationThreshold &&
-                        mDischargeStartLevel - mHealthInfo.legacy.batteryLevel >= dischargeThreshold) {
+                        mDischargeStartLevel - mHealthInfo.batteryLevel >= dischargeThreshold) {
                     // If the discharge cycle is bad enough we want to know about it.
                     logBatteryStatsLocked();
                 }
                 if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
                         " discharge threshold: " + dischargeThreshold);
                 if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
-                        (mDischargeStartLevel - mHealthInfo.legacy.batteryLevel));
+                        (mDischargeStartLevel - mHealthInfo.batteryLevel));
             } catch (NumberFormatException e) {
                 Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
                         durationThresholdString + " or " + dischargeThresholdString);
@@ -700,14 +708,14 @@
     }
 
     private int getIconLocked(int level) {
-        if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+        if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
             return com.android.internal.R.drawable.stat_sys_battery_charge;
-        } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+        } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
             return com.android.internal.R.drawable.stat_sys_battery;
-        } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
-                || mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+        } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+                || mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
             if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
-                    && mHealthInfo.legacy.batteryLevel >= 100) {
+                    && mHealthInfo.batteryLevel >= 100) {
                 return com.android.internal.R.drawable.stat_sys_battery_charge;
             } else {
                 return com.android.internal.R.drawable.stat_sys_battery;
@@ -771,9 +779,9 @@
                 if (!mUpdatesStopped) {
                     copy(mLastHealthInfo, mHealthInfo);
                 }
-                mHealthInfo.legacy.chargerAcOnline = false;
-                mHealthInfo.legacy.chargerUsbOnline = false;
-                mHealthInfo.legacy.chargerWirelessOnline = false;
+                mHealthInfo.chargerAcOnline = false;
+                mHealthInfo.chargerUsbOnline = false;
+                mHealthInfo.chargerWirelessOnline = false;
                 long ident = Binder.clearCallingIdentity();
                 try {
                     mUpdatesStopped = true;
@@ -805,25 +813,25 @@
                     boolean update = true;
                     switch (key) {
                         case "present":
-                            mHealthInfo.legacy.batteryPresent = Integer.parseInt(value) != 0;
+                            mHealthInfo.batteryPresent = Integer.parseInt(value) != 0;
                             break;
                         case "ac":
-                            mHealthInfo.legacy.chargerAcOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.chargerAcOnline = Integer.parseInt(value) != 0;
                             break;
                         case "usb":
-                            mHealthInfo.legacy.chargerUsbOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.chargerUsbOnline = Integer.parseInt(value) != 0;
                             break;
                         case "wireless":
-                            mHealthInfo.legacy.chargerWirelessOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.chargerWirelessOnline = Integer.parseInt(value) != 0;
                             break;
                         case "status":
-                            mHealthInfo.legacy.batteryStatus = Integer.parseInt(value);
+                            mHealthInfo.batteryStatus = Integer.parseInt(value);
                             break;
                         case "level":
-                            mHealthInfo.legacy.batteryLevel = Integer.parseInt(value);
+                            mHealthInfo.batteryLevel = Integer.parseInt(value);
                             break;
                         case "temp":
-                            mHealthInfo.legacy.batteryTemperature = Integer.parseInt(value);
+                            mHealthInfo.batteryTemperature = Integer.parseInt(value);
                             break;
                         case "invalid":
                             mInvalidCharger = Integer.parseInt(value);
@@ -882,20 +890,20 @@
                 if (mUpdatesStopped) {
                     pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
                 }
-                pw.println("  AC powered: " + mHealthInfo.legacy.chargerAcOnline);
-                pw.println("  USB powered: " + mHealthInfo.legacy.chargerUsbOnline);
-                pw.println("  Wireless powered: " + mHealthInfo.legacy.chargerWirelessOnline);
-                pw.println("  Max charging current: " + mHealthInfo.legacy.maxChargingCurrent);
-                pw.println("  Max charging voltage: " + mHealthInfo.legacy.maxChargingVoltage);
-                pw.println("  Charge counter: " + mHealthInfo.legacy.batteryChargeCounter);
-                pw.println("  status: " + mHealthInfo.legacy.batteryStatus);
-                pw.println("  health: " + mHealthInfo.legacy.batteryHealth);
-                pw.println("  present: " + mHealthInfo.legacy.batteryPresent);
-                pw.println("  level: " + mHealthInfo.legacy.batteryLevel);
+                pw.println("  AC powered: " + mHealthInfo.chargerAcOnline);
+                pw.println("  USB powered: " + mHealthInfo.chargerUsbOnline);
+                pw.println("  Wireless powered: " + mHealthInfo.chargerWirelessOnline);
+                pw.println("  Max charging current: " + mHealthInfo.maxChargingCurrent);
+                pw.println("  Max charging voltage: " + mHealthInfo.maxChargingVoltage);
+                pw.println("  Charge counter: " + mHealthInfo.batteryChargeCounter);
+                pw.println("  status: " + mHealthInfo.batteryStatus);
+                pw.println("  health: " + mHealthInfo.batteryHealth);
+                pw.println("  present: " + mHealthInfo.batteryPresent);
+                pw.println("  level: " + mHealthInfo.batteryLevel);
                 pw.println("  scale: " + BATTERY_SCALE);
-                pw.println("  voltage: " + mHealthInfo.legacy.batteryVoltage);
-                pw.println("  temperature: " + mHealthInfo.legacy.batteryTemperature);
-                pw.println("  technology: " + mHealthInfo.legacy.batteryTechnology);
+                pw.println("  voltage: " + mHealthInfo.batteryVoltage);
+                pw.println("  temperature: " + mHealthInfo.batteryTemperature);
+                pw.println("  technology: " + mHealthInfo.batteryTechnology);
             } else {
                 Shell shell = new Shell();
                 shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -909,29 +917,37 @@
         synchronized (mLock) {
             proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
             int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE;
-            if (mHealthInfo.legacy.chargerAcOnline) {
+            if (mHealthInfo.chargerAcOnline) {
                 batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC;
-            } else if (mHealthInfo.legacy.chargerUsbOnline) {
+            } else if (mHealthInfo.chargerUsbOnline) {
                 batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB;
-            } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+            } else if (mHealthInfo.chargerWirelessOnline) {
                 batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS;
             }
             proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
-            proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
-            proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
-            proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
-            proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.legacy.batteryStatus);
-            proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.legacy.batteryHealth);
-            proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.legacy.batteryPresent);
-            proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.legacy.batteryLevel);
+            proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+            proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+            proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
+            proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.batteryStatus);
+            proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.batteryHealth);
+            proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.batteryPresent);
+            proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.batteryLevel);
             proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE);
-            proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.legacy.batteryVoltage);
-            proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
-            proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+            proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.batteryVoltage);
+            proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.batteryTemperature);
+            proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.batteryTechnology);
         }
         proto.flush();
     }
 
+    private static void traceBegin(String name) {
+        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
+    }
+
+    private static void traceEnd() {
+        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+    }
+
     private final class Led {
         private final Light mBatteryLight;
 
@@ -960,8 +976,8 @@
          * Synchronize on BatteryService.
          */
         public void updateLightsLocked() {
-            final int level = mHealthInfo.legacy.batteryLevel;
-            final int status = mHealthInfo.legacy.batteryStatus;
+            final int level = mHealthInfo.batteryLevel;
+            final int status = mHealthInfo.batteryStatus;
             if (level < mLowBatteryWarningLevel) {
                 if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
                     // Solid red when battery is charging
@@ -997,6 +1013,7 @@
                 String instance) {
             if (newService == null) return;
 
+            traceBegin("HealthUnregisterCallback");
             try {
                 if (oldService != null) {
                     int r = oldService.unregisterCallback(this);
@@ -1008,8 +1025,11 @@
             } catch (RemoteException ex) {
                 Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
                             + ex.getMessage());
+            } finally {
+                traceEnd();
             }
 
+            traceBegin("HealthRegisterCallback");
             try {
                 int r = newService.registerCallback(this);
                 if (r != Result.SUCCESS) {
@@ -1022,6 +1042,8 @@
             } catch (RemoteException ex) {
                 Slog.e(TAG, "health: cannot register callback (transaction error): "
                         + ex.getMessage());
+            } finally {
+                traceEnd();
             }
         }
     }
@@ -1054,53 +1076,63 @@
             Slog.e(TAG, "health: must not call unregisterListener on battery properties");
         }
         public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
-            IHealth service = mHealthServiceWrapper.getLastService();
-            if (service == null) throw new RemoteException("no health service");
-            final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
-            switch(id) {
-                case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
-                    service.getChargeCounter((int result, int value) -> {
-                        outResult.value = result;
-                        if (result == Result.SUCCESS) prop.setLong(value);
-                    });
-                    break;
-                case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
-                    service.getCurrentNow((int result, int value) -> {
-                        outResult.value = result;
-                        if (result == Result.SUCCESS) prop.setLong(value);
-                    });
-                    break;
-                case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
-                    service.getCurrentAverage((int result, int value) -> {
-                        outResult.value = result;
-                        if (result == Result.SUCCESS) prop.setLong(value);
-                    });
-                    break;
-                case BatteryManager.BATTERY_PROPERTY_CAPACITY:
-                    service.getCapacity((int result, int value) -> {
-                        outResult.value = result;
-                        if (result == Result.SUCCESS) prop.setLong(value);
-                    });
-                    break;
-                case BatteryManager.BATTERY_PROPERTY_STATUS:
-                    service.getChargeStatus((int result, int value) -> {
-                        outResult.value = result;
-                        if (result == Result.SUCCESS) prop.setLong(value);
-                    });
-                    break;
-                case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
-                    service.getEnergyCounter((int result, long value) -> {
-                        outResult.value = result;
-                        if (result == Result.SUCCESS) prop.setLong(value);
-                    });
-                    break;
+            traceBegin("HealthGetProperty");
+            try {
+                IHealth service = mHealthServiceWrapper.getLastService();
+                if (service == null) throw new RemoteException("no health service");
+                final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
+                switch(id) {
+                    case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
+                        service.getChargeCounter((int result, int value) -> {
+                            outResult.value = result;
+                            if (result == Result.SUCCESS) prop.setLong(value);
+                        });
+                        break;
+                    case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
+                        service.getCurrentNow((int result, int value) -> {
+                            outResult.value = result;
+                            if (result == Result.SUCCESS) prop.setLong(value);
+                        });
+                        break;
+                    case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
+                        service.getCurrentAverage((int result, int value) -> {
+                            outResult.value = result;
+                            if (result == Result.SUCCESS) prop.setLong(value);
+                        });
+                        break;
+                    case BatteryManager.BATTERY_PROPERTY_CAPACITY:
+                        service.getCapacity((int result, int value) -> {
+                            outResult.value = result;
+                            if (result == Result.SUCCESS) prop.setLong(value);
+                        });
+                        break;
+                    case BatteryManager.BATTERY_PROPERTY_STATUS:
+                        service.getChargeStatus((int result, int value) -> {
+                            outResult.value = result;
+                            if (result == Result.SUCCESS) prop.setLong(value);
+                        });
+                        break;
+                    case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
+                        service.getEnergyCounter((int result, long value) -> {
+                            outResult.value = result;
+                            if (result == Result.SUCCESS) prop.setLong(value);
+                        });
+                        break;
+                }
+                return outResult.value;
+            } finally {
+                traceEnd();
             }
-            return outResult.value;
         }
         public void scheduleUpdate() throws RemoteException {
-            IHealth service = mHealthServiceWrapper.getLastService();
-            if (service == null) throw new RemoteException("no health service");
-            service.update();
+            traceBegin("HealthScheduleUpdate");
+            try {
+                IHealth service = mHealthServiceWrapper.getLastService();
+                if (service == null) throw new RemoteException("no health service");
+                service.update();
+            } finally {
+                traceEnd();
+            }
         }
     }
 
@@ -1122,7 +1154,7 @@
         @Override
         public int getBatteryLevel() {
             synchronized (mLock) {
-                return mHealthInfo.legacy.batteryLevel;
+                return mHealthInfo.batteryLevel;
             }
         }
 
@@ -1163,12 +1195,13 @@
                 Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
 
         private final IServiceNotification mNotification = new Notification();
+        private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceRefresh");
+        // These variables are fixed after init.
         private Callback mCallback;
         private IHealthSupplier mHealthSupplier;
+        private String mInstanceName;
 
-        private final Object mLastServiceSetLock = new Object();
         // Last IHealth service received.
-        // set must be also be guarded with mLastServiceSetLock to ensure ordering.
         private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
 
         /**
@@ -1186,6 +1219,10 @@
          * Start monitoring registration of new IHealth services. Only instances that are in
          * {@code sAllInstances} and in device / framework manifest are used. This function should
          * only be called once.
+         *
+         * mCallback.onRegistration() is called synchronously (aka in init thread) before
+         * this method returns.
+         *
          * @throws RemoteException transaction error when talking to IServiceManager
          * @throws NoSuchElementException if one of the following cases:
          *         - No service manager;
@@ -1201,24 +1238,51 @@
             if (callback == null || managerSupplier == null || healthSupplier == null)
                 throw new NullPointerException();
 
+            IServiceManager manager;
+
             mCallback = callback;
             mHealthSupplier = healthSupplier;
 
-            IServiceManager manager = managerSupplier.get();
+            // Initialize mLastService and call callback for the first time (in init thread)
+            IHealth newService = null;
             for (String name : sAllInstances) {
-                if (manager.getTransport(IHealth.kInterfaceName, name) ==
-                        IServiceManager.Transport.EMPTY) {
-                    continue;
+                traceBegin("HealthInitGetService_" + name);
+                try {
+                    newService = healthSupplier.get(name);
+                } catch (NoSuchElementException ex) {
+                    /* ignored, handled below */
+                } finally {
+                    traceEnd();
                 }
-
-                manager.registerForNotifications(IHealth.kInterfaceName, name, mNotification);
-                Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + name);
-                return;
+                if (newService != null) {
+                    mInstanceName = name;
+                    mLastService.set(newService);
+                    break;
+                }
             }
 
-            throw new NoSuchElementException(String.format(
-                    "No IHealth service instance among %s is available. Perhaps no permission?",
-                    sAllInstances.toString()));
+            if (mInstanceName == null || newService == null) {
+                throw new NoSuchElementException(String.format(
+                        "No IHealth service instance among %s is available. Perhaps no permission?",
+                        sAllInstances.toString()));
+            }
+            mCallback.onRegistration(null, newService, mInstanceName);
+
+            // Register for future service registrations
+            traceBegin("HealthInitRegisterNotification");
+            mHandlerThread.start();
+            try {
+                managerSupplier.get().registerForNotifications(
+                        IHealth.kInterfaceName, mInstanceName, mNotification);
+            } finally {
+                traceEnd();
+            }
+            Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
+        }
+
+        @VisibleForTesting
+        HandlerThread getHandlerThread() {
+            return mHandlerThread;
         }
 
         interface Callback {
@@ -1249,7 +1313,7 @@
          */
         interface IHealthSupplier {
             default IHealth get(String name) throws NoSuchElementException, RemoteException {
-                return IHealth.getService(name);
+                return IHealth.getService(name, true /* retry */);
             }
         }
 
@@ -1258,19 +1322,28 @@
             public final void onRegistration(String interfaceName, String instanceName,
                     boolean preexisting) {
                 if (!IHealth.kInterfaceName.equals(interfaceName)) return;
-                if (!sAllInstances.contains(instanceName)) return;
-                try {
-                    // ensures the order of multiple onRegistration on different threads.
-                    synchronized (mLastServiceSetLock) {
-                        IHealth newService = mHealthSupplier.get(instanceName);
-                        IHealth oldService = mLastService.getAndSet(newService);
-                        Slog.i(TAG, "health: new instance registered " + instanceName);
-                        mCallback.onRegistration(oldService, newService, instanceName);
+                if (!mInstanceName.equals(instanceName)) return;
+
+                // This runnable only runs on mHandlerThread and ordering is ensured, hence
+                // no locking is needed inside the runnable.
+                mHandlerThread.getThreadHandler().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            IHealth newService = mHealthSupplier.get(mInstanceName);
+                            IHealth oldService = mLastService.getAndSet(newService);
+
+                            // preexisting may be inaccurate (race). Check for equality here.
+                            if (Objects.equals(newService, oldService)) return;
+
+                            Slog.i(TAG, "health: new instance registered " + mInstanceName);
+                            mCallback.onRegistration(oldService, newService, mInstanceName);
+                        } catch (NoSuchElementException | RemoteException ex) {
+                            Slog.e(TAG, "health: Cannot get instance '" + mInstanceName
+                                    + "': " + ex.getMessage() + ". Perhaps no permission?");
+                        }
                     }
-                } catch (NoSuchElementException | RemoteException ex) {
-                    Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
-                           ex.getMessage() + ". Perhaps no permission?");
-                }
+                });
             }
         }
     }
diff --git a/com/android/server/BluetoothManagerService.java b/com/android/server/BluetoothManagerService.java
index 75206e4..c34c30c 100644
--- a/com/android/server/BluetoothManagerService.java
+++ b/com/android/server/BluetoothManagerService.java
@@ -195,6 +195,7 @@
     private LinkedList<ActiveLog> mActiveLogs;
     private LinkedList<Long> mCrashTimestamps;
     private int mCrashes;
+    private long mLastEnabledTime;
 
     // configuration from external IBinder call which is used to
     // synchronize with broadcast receiver.
@@ -2021,6 +2022,7 @@
         mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,
                              quietMode ? 1 : 0, 0));
         addActiveLog(packageName, true);
+        mLastEnabledTime = SystemClock.elapsedRealtime();
     }
 
     private void addActiveLog(String packageName, boolean enable) {
@@ -2142,7 +2144,7 @@
             writer.println("  address: " + mAddress);
             writer.println("  name: " + mName);
             if (mEnable) {
-                long onDuration = System.currentTimeMillis() - mActiveLogs.getLast().getTime();
+                long onDuration = SystemClock.elapsedRealtime() - mLastEnabledTime;
                 String onDurationString = String.format("%02d:%02d:%02d.%03d",
                                           (int)(onDuration / (1000 * 60 * 60)),
                                           (int)((onDuration / (1000 * 60)) % 60),
diff --git a/com/android/server/ConnectivityService.java b/com/android/server/ConnectivityService.java
index 7e65d36..bccae06 100644
--- a/com/android/server/ConnectivityService.java
+++ b/com/android/server/ConnectivityService.java
@@ -29,7 +29,10 @@
 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_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.Nullable;
@@ -70,6 +73,7 @@
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.Uri;
+import android.net.VpnService;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.util.MultinetworkPolicyTracker;
@@ -2119,9 +2123,14 @@
                         final boolean valid =
                                 (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
                         final boolean wasValidated = nai.lastValidated;
+                        final boolean wasDefault = isDefaultNetwork(nai);
                         if (DBG) log(nai.name() + " validation " + (valid ? "passed" : "failed") +
                                 (msg.obj == null ? "" : " with redirect to " + (String)msg.obj));
                         if (valid != nai.lastValidated) {
+                            if (wasDefault) {
+                                metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity(
+                                        SystemClock.elapsedRealtime(), valid);
+                            }
                             final int oldScore = nai.getCurrentScore();
                             nai.lastValidated = valid;
                             nai.everValidated |= valid;
@@ -2293,7 +2302,8 @@
                 // Let rematchAllNetworksAndRequests() below record a new default network event
                 // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
                 // whose timestamps tell how long it takes to recover a default network.
-                metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai);
+                long now = SystemClock.elapsedRealtime();
+                metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai);
             }
             notifyIfacesChangedForNetworkStats();
             // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -4675,10 +4685,12 @@
             }
         }
 
-        final NetworkCapabilities prevNc = nai.networkCapabilities;
+        final NetworkCapabilities prevNc;
         synchronized (nai) {
+            prevNc = nai.networkCapabilities;
             nai.networkCapabilities = networkCapabilities;
         }
+
         if (nai.getCurrentScore() == oldScore &&
                 networkCapabilities.equalRequestableCapabilities(prevNc)) {
             // If the requestable capabilities haven't changed, and the score hasn't changed, then
@@ -4692,6 +4704,28 @@
             rematchAllNetworksAndRequests(nai, oldScore);
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
+
+        // Report changes that are interesting for network statistics tracking.
+        if (prevNc != null) {
+            final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) !=
+                    networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED);
+            final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) !=
+                    networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+            if (meteredChanged || roamingChanged) {
+                notifyIfacesChangedForNetworkStats();
+            }
+        }
+
+        if (!networkCapabilities.hasTransport(TRANSPORT_VPN)) {
+            // Tell VPNs about updated capabilities, since they may need to
+            // bubble those changes through.
+            synchronized (mVpns) {
+                for (int i = 0; i < mVpns.size(); i++) {
+                    final Vpn vpn = mVpns.valueAt(i);
+                    vpn.updateCapabilities();
+                }
+            }
+        }
     }
 
     public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) {
@@ -5024,7 +5058,7 @@
             makeDefault(newNetwork);
             // Log 0 -> X and Y -> X default network transitions, where X is the new default.
             metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
-                    newNetwork, oldDefaultNetwork);
+                    now, newNetwork, oldDefaultNetwork);
             // Have a new default network, release the transition wakelock in
             scheduleReleaseNetworkTransitionWakelock();
         }
@@ -5225,14 +5259,6 @@
         }
         notifyLockdownVpn(networkAgent);
 
-        if (oldInfo != null && oldInfo.getState() == state) {
-            if (oldInfo.isRoaming() != newInfo.isRoaming()) {
-                if (VDBG) log("roaming status changed, notifying NetworkStatsService");
-                notifyIfacesChangedForNetworkStats();
-            } else if (VDBG) log("ignoring duplicate network state non-change");
-            // In either case, no further work should be needed.
-            return;
-        }
         if (DBG) {
             log(networkAgent.name() + " EVENT_NETWORK_INFO_CHANGED, going from " +
                     (oldInfo == null ? "null" : oldInfo.getState()) +
@@ -5590,7 +5616,8 @@
     }
 
     private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
-        mMetricsLog.log(new NetworkEvent(nai.network.netId, evtype));
+        int[] transports = nai.networkCapabilities.getTransportTypes();
+        mMetricsLog.log(nai.network.netId, transports, new NetworkEvent(evtype));
     }
 
     private static boolean toBool(int encodedBoolean) {
diff --git a/com/android/server/MountServiceIdler.java b/com/android/server/MountServiceIdler.java
index d8bd0bb..1891ba9 100644
--- a/com/android/server/MountServiceIdler.java
+++ b/com/android/server/MountServiceIdler.java
@@ -72,7 +72,7 @@
             synchronized (mFinishCallback) {
                 mStarted = true;
             }
-            ms.runIdleMaintenance(mFinishCallback);
+            ms.runIdleMaint(mFinishCallback);
         }
         return ms != null;
     }
@@ -82,8 +82,12 @@
         // Once we kick off the fstrim we aren't actually interruptible; just note
         // that we don't need to call jobFinished(), and let everything happen in
         // the callback from the mount service.
-        synchronized (mFinishCallback) {
-            mStarted = false;
+        StorageManagerService ms = StorageManagerService.sSelf;
+        if (ms != null) {
+            ms.abortIdleMaint(mFinishCallback);
+            synchronized (mFinishCallback) {
+                mStarted = false;
+            }
         }
         return false;
     }
diff --git a/com/android/server/NetworkManagementService.java b/com/android/server/NetworkManagementService.java
index c60d7b0..8a15ded 100644
--- a/com/android/server/NetworkManagementService.java
+++ b/com/android/server/NetworkManagementService.java
@@ -20,6 +20,9 @@
 import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.SHUTDOWN;
+import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
 import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_NONE;
@@ -92,6 +95,7 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -1946,9 +1950,9 @@
     public void setDnsConfigurationForNetwork(int netId, String[] servers, String domains) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
-        ContentResolver resolver = mContext.getContentResolver();
+        final ContentResolver cr = mContext.getContentResolver();
 
-        int sampleValidity = Settings.Global.getInt(resolver,
+        int sampleValidity = Settings.Global.getInt(cr,
                 Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
                 DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
         if (sampleValidity < 0 || sampleValidity > 65535) {
@@ -1957,7 +1961,7 @@
             sampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS;
         }
 
-        int successThreshold = Settings.Global.getInt(resolver,
+        int successThreshold = Settings.Global.getInt(cr,
                 Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
                 DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
         if (successThreshold < 0 || successThreshold > 100) {
@@ -1966,9 +1970,9 @@
             successThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
         }
 
-        int minSamples = Settings.Global.getInt(resolver,
+        int minSamples = Settings.Global.getInt(cr,
                 Settings.Global.DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
-        int maxSamples = Settings.Global.getInt(resolver,
+        int maxSamples = Settings.Global.getInt(cr,
                 Settings.Global.DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
         if (minSamples < 0 || minSamples > maxSamples || maxSamples > 64) {
             Slog.w(TAG, "Invalid sample count (min, max)=(" + minSamples + ", " + maxSamples +
@@ -1980,8 +1984,24 @@
 
         final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
         final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
-        final boolean useTls = Settings.Global.getInt(resolver,
-                Settings.Global.DNS_TLS_DISABLED, 0) == 0;
+        final boolean useTls = shouldUseTls(cr);
+        // TODO: Populate tlsHostname once it's decided how the hostname's IP
+        // addresses will be resolved:
+        //
+        //     [1] network-provided DNS servers are included here with the
+        //         hostname and netd will use the network-provided servers to
+        //         resolve the hostname and fix up its internal structures, or
+        //
+        //     [2] network-provided DNS servers are included here without the
+        //         hostname, the ConnectivityService layer resolves the given
+        //         hostname, and then reconfigures netd with this information.
+        //
+        // In practice, there will always be a need for ConnectivityService or
+        // the captive portal app to use the network-provided services to make
+        // some queries. This argues in favor of [1], in concert with another
+        // mechanism, perhaps setting a high bit in the netid, to indicate
+        // via existing DNS APIs which set of servers (network-provided or
+        // non-network-provided private DNS) should be queried.
         final String tlsHostname = "";
         final String[] tlsFingerprints = new String[0];
         try {
@@ -1992,6 +2012,15 @@
         }
     }
 
+    private static boolean shouldUseTls(ContentResolver cr) {
+        String privateDns = Settings.Global.getString(cr, Settings.Global.PRIVATE_DNS_MODE);
+        if (TextUtils.isEmpty(privateDns)) {
+            privateDns = PRIVATE_DNS_DEFAULT_MODE;
+        }
+        return privateDns.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) ||
+               privateDns.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+    }
+
     @Override
     public void addVpnUidRanges(int netId, UidRange[] ranges) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/com/android/server/RescueParty.java b/com/android/server/RescueParty.java
index 1924a86..a9f190c 100644
--- a/com/android/server/RescueParty.java
+++ b/com/android/server/RescueParty.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Build;
@@ -146,7 +148,7 @@
         SystemProperties.set(PROP_RESCUE_LEVEL, Integer.toString(level));
 
         EventLogTags.writeRescueLevel(level, triggerUid);
-        PackageManagerService.logCriticalInfo(Log.WARN, "Incremented rescue level to "
+        logCriticalInfo(Log.WARN, "Incremented rescue level to "
                 + levelToString(level) + " triggered by UID " + triggerUid);
     }
 
@@ -166,12 +168,12 @@
         try {
             executeRescueLevelInternal(context, level);
             EventLogTags.writeRescueSuccess(level);
-            PackageManagerService.logCriticalInfo(Log.DEBUG,
+            logCriticalInfo(Log.DEBUG,
                     "Finished rescue level " + levelToString(level));
         } catch (Throwable t) {
             final String msg = ExceptionUtils.getCompleteMessage(t);
             EventLogTags.writeRescueFailure(level, msg);
-            PackageManagerService.logCriticalInfo(Log.ERROR,
+            logCriticalInfo(Log.ERROR,
                     "Failed rescue level " + levelToString(level) + ": " + msg);
         }
     }
diff --git a/com/android/server/ServiceThread.java b/com/android/server/ServiceThread.java
index bce64af..26703c5 100644
--- a/com/android/server/ServiceThread.java
+++ b/com/android/server/ServiceThread.java
@@ -19,7 +19,6 @@
 import android.os.HandlerThread;
 import android.os.Process;
 import android.os.StrictMode;
-import android.util.Slog;
 
 /**
  * Special handler thread that we create for system services that require their own loopers.
@@ -38,11 +37,10 @@
     public void run() {
         Process.setCanSelfBackground(false);
 
-        // For debug builds, log event loop stalls to dropbox for analysis.
-        if (!mAllowIo && StrictMode.conditionallyEnableDebugLogging()) {
-            Slog.i(TAG, "Enabled StrictMode logging for " + getName() + " looper.");
+        if (!mAllowIo) {
+            StrictMode.initThreadDefaults(null);
         }
 
         super.run();
     }
-}
\ No newline at end of file
+}
diff --git a/com/android/server/ServiceWatcher.java b/com/android/server/ServiceWatcher.java
index 2ff036b..f20ca43 100644
--- a/com/android/server/ServiceWatcher.java
+++ b/com/android/server/ServiceWatcher.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -397,9 +398,29 @@
         }
     }
 
-    public @Nullable IBinder getBinder() {
+    /**
+     * The runner that runs on the binder retrieved from {@link ServiceWatcher}.
+     */
+    public interface BinderRunner {
+        /**
+         * Runs on the retrieved binder.
+         * @param binder the binder retrieved from the {@link ServiceWatcher}.
+         */
+        public void run(@NonNull IBinder binder);
+    }
+
+    /**
+     * Retrieves the binder from {@link ServiceWatcher} and runs it.
+     * @return whether a valid service exists.
+     */
+    public boolean runOnBinder(@NonNull BinderRunner runner) {
         synchronized (mLock) {
-            return mBoundService;
+            if (mBoundService == null) {
+                return false;
+            } else {
+                runner.run(mBoundService);
+                return true;
+            }
         }
     }
 
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index bfbce40..3c955eb 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.IProgressListener;
 import android.os.IStoraged;
 import android.os.IVold;
 import android.os.IVoldListener;
@@ -541,6 +542,8 @@
     private static final int H_VOLUME_UNMOUNT = 8;
     private static final int H_PARTITION_FORGET = 9;
     private static final int H_RESET = 10;
+    private static final int H_RUN_IDLE_MAINT = 11;
+    private static final int H_ABORT_IDLE_MAINT = 12;
 
     class StorageManagerServiceHandler extends Handler {
         public StorageManagerServiceHandler(Looper looper) {
@@ -570,7 +573,7 @@
                     }
 
                     // TODO: Reintroduce shouldBenchmark() test
-                    fstrim(0);
+                    fstrim(0, null);
 
                     // invoke the completion callback, if any
                     // TODO: fstrim is non-blocking, so remove this useless callback
@@ -649,6 +652,17 @@
                     resetIfReadyAndConnected();
                     break;
                 }
+                case H_RUN_IDLE_MAINT: {
+                    Slog.i(TAG, "Running idle maintenance");
+                    runIdleMaint((Runnable)msg.obj);
+                    break;
+                }
+                case H_ABORT_IDLE_MAINT: {
+                    Slog.i(TAG, "Aborting idle maintenance");
+                    abortIdleMaint((Runnable)msg.obj);
+                    break;
+                }
+
             }
         }
     }
@@ -1576,21 +1590,19 @@
     }
 
     @Override
-    public long benchmark(String volId) {
+    public void benchmark(String volId, IVoldTaskListener listener) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
 
-        // TODO: refactor for callers to provide a listener
         try {
-            final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
             mVold.benchmark(volId, new IVoldTaskListener.Stub() {
                 @Override
                 public void onStatus(int status, PersistableBundle extras) {
-                    // Not currently used
+                    dispatchOnStatus(listener, status, extras);
                 }
 
                 @Override
                 public void onFinished(int status, PersistableBundle extras) {
-                    result.complete(extras);
+                    dispatchOnFinished(listener, status, extras);
 
                     final String path = extras.getString("path");
                     final String ident = extras.getString("ident");
@@ -1611,10 +1623,8 @@
                     }
                 }
             });
-            return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE);
-        } catch (Exception e) {
-            Slog.wtf(TAG, e);
-            return Long.MAX_VALUE;
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -1742,13 +1752,15 @@
     }
 
     @Override
-    public void fstrim(int flags) {
+    public void fstrim(int flags, IVoldTaskListener listener) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
 
         try {
             mVold.fstrim(flags, new IVoldTaskListener.Stub() {
                 @Override
                 public void onStatus(int status, PersistableBundle extras) {
+                    dispatchOnStatus(listener, status, extras);
+
                     // Ignore trim failures
                     if (status != 0) return;
 
@@ -1770,15 +1782,68 @@
 
                 @Override
                 public void onFinished(int status, PersistableBundle extras) {
-                    // Not currently used
+                    dispatchOnFinished(listener, status, extras);
+
                     // TODO: benchmark when desired
                 }
             });
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    void runIdleMaint(Runnable callback) {
+        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+        try {
+            mVold.runIdleMaint(new IVoldTaskListener.Stub() {
+                @Override
+                public void onStatus(int status, PersistableBundle extras) {
+                    // Not currently used
+                }
+                @Override
+                public void onFinished(int status, PersistableBundle extras) {
+                    if (callback != null) {
+                        BackgroundThread.getHandler().post(callback);
+                    }
+                }
+            });
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
     }
 
+    @Override
+    public void runIdleMaintenance() {
+        runIdleMaint(null);
+    }
+
+    void abortIdleMaint(Runnable callback) {
+        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+
+        try {
+            mVold.abortIdleMaint(new IVoldTaskListener.Stub() {
+                @Override
+                public void onStatus(int status, PersistableBundle extras) {
+                    // Not currently used
+                }
+                @Override
+                public void onFinished(int status, PersistableBundle extras) {
+                    if (callback != null) {
+                        BackgroundThread.getHandler().post(callback);
+                    }
+                }
+            });
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+        }
+    }
+
+    @Override
+    public void abortIdleMaintenance() {
+        abortIdleMaint(null);
+    }
+
     private void remountUidExternalStorage(int uid, int mode) {
         try {
             mVold.remountUid(uid, mode);
@@ -3239,6 +3304,26 @@
         }
     }
 
+    private void dispatchOnStatus(IVoldTaskListener listener, int status,
+            PersistableBundle extras) {
+        if (listener != null) {
+            try {
+                listener.onStatus(status, extras);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    private void dispatchOnFinished(IVoldTaskListener listener, int status,
+            PersistableBundle extras) {
+        if (listener != null) {
+            try {
+                listener.onFinished(status, extras);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
     private static class Callbacks extends Handler {
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index de5d879..74a7bd4 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -37,7 +37,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.SystemClock;
@@ -52,7 +51,7 @@
 import android.view.WindowManager;
 
 import com.android.internal.R;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BinderInternal;
@@ -69,7 +68,7 @@
 import com.android.server.coverage.CoverageService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
 import com.android.server.display.DisplayManagerService;
-import com.android.server.display.NightDisplayService;
+import com.android.server.display.ColorDisplayService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.emergency.EmergencyAffordanceService;
 import com.android.server.fingerprint.FingerprintService;
@@ -83,6 +82,7 @@
 import com.android.server.media.projection.MediaProjectionManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
+import com.android.server.net.watchlist.NetworkWatchlistService;
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.oemlock.OemLockService;
 import com.android.server.om.OverlayManagerService;
@@ -95,6 +95,7 @@
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.ShortcutService;
 import com.android.server.pm.UserManagerService;
+import com.android.server.pm.crossprofile.CrossProfileAppsService;
 import com.android.server.policy.PhoneWindowManager;
 import com.android.server.power.PowerManagerService;
 import com.android.server.power.ShutdownThread;
@@ -193,6 +194,8 @@
             "com.google.android.clockwork.ThermalObserver";
     private static final String WEAR_CONNECTIVITY_SERVICE_CLASS =
             "com.google.android.clockwork.connectivity.WearConnectivityService";
+    private static final String WEAR_SIDEKICK_SERVICE_CLASS =
+            "com.google.android.clockwork.sidekick.SidekickService";
     private static final String WEAR_DISPLAY_SERVICE_CLASS =
             "com.google.android.clockwork.display.WearDisplayService";
     private static final String WEAR_LEFTY_SERVICE_CLASS =
@@ -404,10 +407,8 @@
             traceEnd();
         }
 
-        // For debug builds, log event loop stalls to dropbox for analysis.
-        if (StrictMode.conditionallyEnableDebugLogging()) {
-            Slog.i(TAG, "Enabled StrictMode for system server main thread.");
-        }
+        StrictMode.initVmDefaults(null);
+
         if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
             int uptimeMillis = (int) SystemClock.elapsedRealtime();
             MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
@@ -545,11 +546,9 @@
         traceEnd();
 
         // Bring up recovery system in case a rescue party needs a reboot
-        if (!SystemProperties.getBoolean("config.disable_noncore", false)) {
-            traceBeginAndSlog("StartRecoverySystemService");
-            mSystemServiceManager.startService(RecoverySystemService.class);
-            traceEnd();
-        }
+        traceBeginAndSlog("StartRecoverySystemService");
+        mSystemServiceManager.startService(RecoverySystemService.class);
+        traceEnd();
 
         // Now that we have the bare essentials of the OS up and running, take
         // note that we just booted, which might send out a rescue party if
@@ -561,6 +560,13 @@
         mSystemServiceManager.startService(LightsService.class);
         traceEnd();
 
+        traceBeginAndSlog("StartSidekickService");
+        // Package manager isn't started yet; need to use SysProp not hardware feature
+        if (SystemProperties.getBoolean("config.enable_sidekick_graphics", false)) {
+            mSystemServiceManager.startService(WEAR_SIDEKICK_SERVICE_CLASS);
+        }
+        traceEnd();
+
         // Display manager is needed to provide display metrics before package manager
         // starts up.
         traceBeginAndSlog("StartDisplayManager");
@@ -705,13 +711,7 @@
         MmsServiceBroker mmsService = null;
         HardwarePropertiesManagerService hardwarePropertiesService = null;
 
-        boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
-        boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
-        boolean disableLocation = SystemProperties.getBoolean("config.disable_location", false);
         boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false);
-        boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false);
-        boolean disableNetwork = SystemProperties.getBoolean("config.disable_network", false);
-        boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime", false);
         boolean disableRtt = SystemProperties.getBoolean("config.disable_rtt", false);
         boolean disableMediaProjection = SystemProperties.getBoolean("config.disable_mediaproj",
                 false);
@@ -875,8 +875,6 @@
             } else if (!context.getPackageManager().hasSystemFeature
                        (PackageManager.FEATURE_BLUETOOTH)) {
                 Slog.i(TAG, "No Bluetooth Service (Bluetooth Hardware Not Present)");
-            } else if (disableBluetooth) {
-                Slog.i(TAG, "Bluetooth Service disabled by config");
             } else {
                 traceBeginAndSlog("StartBluetoothService");
                 mSystemServiceManager.startService(BluetoothService.class);
@@ -887,6 +885,10 @@
             mSystemServiceManager.startService(IpConnectivityMetrics.class);
             traceEnd();
 
+            traceBeginAndSlog("NetworkWatchlistService");
+            mSystemServiceManager.startService(NetworkWatchlistService.Lifecycle.class);
+            traceEnd();
+
             traceBeginAndSlog("PinnerService");
             mSystemServiceManager.startService(PinnerService.class);
             traceEnd();
@@ -927,8 +929,7 @@
         traceEnd();
 
         if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
-            if (!disableStorage &&
-                    !"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+            if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
                 traceBeginAndSlog("StartStorageManagerService");
                 try {
                     /*
@@ -978,42 +979,40 @@
         traceEnd();
 
         if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartLockSettingsService");
-                try {
-                    mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
-                    lockSettings = ILockSettings.Stub.asInterface(
-                            ServiceManager.getService("lock_settings"));
-                } catch (Throwable e) {
-                    reportWtf("starting LockSettingsService service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartLockSettingsService");
+            try {
+                mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
+                lockSettings = ILockSettings.Stub.asInterface(
+                    ServiceManager.getService("lock_settings"));
+            } catch (Throwable e) {
+                reportWtf("starting LockSettingsService service", e);
+            }
+            traceEnd();
 
-                final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
-                if (hasPdb) {
-                    traceBeginAndSlog("StartPersistentDataBlock");
-                    mSystemServiceManager.startService(PersistentDataBlockService.class);
-                    traceEnd();
-                }
-
-                if (hasPdb || OemLockService.isHalPresent()) {
-                    // Implementation depends on pdb or the OemLock HAL
-                    traceBeginAndSlog("StartOemLockService");
-                    mSystemServiceManager.startService(OemLockService.class);
-                    traceEnd();
-                }
-
-                traceBeginAndSlog("StartDeviceIdleController");
-                mSystemServiceManager.startService(DeviceIdleController.class);
-                traceEnd();
-
-                // Always start the Device Policy Manager, so that the API is compatible with
-                // API8.
-                traceBeginAndSlog("StartDevicePolicyManager");
-                mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+            final boolean hasPdb = !SystemProperties.get(PERSISTENT_DATA_BLOCK_PROP).equals("");
+            if (hasPdb) {
+                traceBeginAndSlog("StartPersistentDataBlock");
+                mSystemServiceManager.startService(PersistentDataBlockService.class);
                 traceEnd();
             }
 
+            if (hasPdb || OemLockService.isHalPresent()) {
+                // Implementation depends on pdb or the OemLock HAL
+                traceBeginAndSlog("StartOemLockService");
+                mSystemServiceManager.startService(OemLockService.class);
+                traceEnd();
+            }
+
+            traceBeginAndSlog("StartDeviceIdleController");
+            mSystemServiceManager.startService(DeviceIdleController.class);
+            traceEnd();
+
+            // Always start the Device Policy Manager, so that the API is compatible with
+            // API8.
+            traceBeginAndSlog("StartDevicePolicyManager");
+            mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
+            traceEnd();
+
             if (!disableSystemUI) {
                 traceBeginAndSlog("StartStatusBarManagerService");
                 try {
@@ -1025,153 +1024,145 @@
                 traceEnd();
             }
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartClipboardService");
-                mSystemServiceManager.startService(ClipboardService.class);
-                traceEnd();
+            traceBeginAndSlog("StartClipboardService");
+            mSystemServiceManager.startService(ClipboardService.class);
+            traceEnd();
+
+            traceBeginAndSlog("StartNetworkManagementService");
+            try {
+                networkManagement = NetworkManagementService.create(context);
+                ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
+            } catch (Throwable e) {
+                reportWtf("starting NetworkManagement Service", e);
             }
+            traceEnd();
 
-            if (!disableNetwork) {
-                traceBeginAndSlog("StartNetworkManagementService");
-                try {
-                    networkManagement = NetworkManagementService.create(context);
-                    ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
-                } catch (Throwable e) {
-                    reportWtf("starting NetworkManagement Service", e);
-                }
-                traceEnd();
-
-                traceBeginAndSlog("StartIpSecService");
-                try {
-                    ipSecService = IpSecService.create(context);
-                    ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
-                } catch (Throwable e) {
-                    reportWtf("starting IpSec Service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartIpSecService");
+            try {
+                ipSecService = IpSecService.create(context);
+                ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+            } catch (Throwable e) {
+                reportWtf("starting IpSec Service", e);
             }
+            traceEnd();
 
-            if (!disableNonCoreServices && !disableTextServices) {
+            if (!disableTextServices) {
                 traceBeginAndSlog("StartTextServicesManager");
                 mSystemServiceManager.startService(TextServicesManagerService.Lifecycle.class);
                 traceEnd();
             }
 
-            if (!disableNetwork) {
-                traceBeginAndSlog("StartNetworkScoreService");
-                try {
-                    networkScore = new NetworkScoreService(context);
-                    ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore);
-                } catch (Throwable e) {
-                    reportWtf("starting Network Score Service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNetworkScoreService");
+            try {
+                networkScore = new NetworkScoreService(context);
+                ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore);
+            } catch (Throwable e) {
+                reportWtf("starting Network Score Service", e);
+            }
+            traceEnd();
 
-                traceBeginAndSlog("StartNetworkStatsService");
-                try {
-                    networkStats = NetworkStatsService.create(context, networkManagement);
-                    ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
-                } catch (Throwable e) {
-                    reportWtf("starting NetworkStats Service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNetworkStatsService");
+            try {
+                networkStats = NetworkStatsService.create(context, networkManagement);
+                ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
+            } catch (Throwable e) {
+                reportWtf("starting NetworkStats Service", e);
+            }
+            traceEnd();
 
-                traceBeginAndSlog("StartNetworkPolicyManagerService");
-                try {
-                    networkPolicy = new NetworkPolicyManagerService(context,
-                            mActivityManagerService, networkStats, networkManagement);
-                    ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
-                } catch (Throwable e) {
-                    reportWtf("starting NetworkPolicy Service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNetworkPolicyManagerService");
+            try {
+                networkPolicy = new NetworkPolicyManagerService(context,
+                    mActivityManagerService, networkStats, networkManagement);
+                ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
+            } catch (Throwable e) {
+                reportWtf("starting NetworkPolicy Service", e);
+            }
+            traceEnd();
 
-                // Wifi Service must be started first for wifi-related services.
-                traceBeginAndSlog("StartWifi");
-                mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
-                traceEnd();
-                traceBeginAndSlog("StartWifiScanning");
-                mSystemServiceManager.startService(
-                        "com.android.server.wifi.scanner.WifiScanningService");
-                traceEnd();
+            // Wifi Service must be started first for wifi-related services.
+            traceBeginAndSlog("StartWifi");
+            mSystemServiceManager.startService(WIFI_SERVICE_CLASS);
+            traceEnd();
+            traceBeginAndSlog("StartWifiScanning");
+            mSystemServiceManager.startService(
+                "com.android.server.wifi.scanner.WifiScanningService");
+            traceEnd();
 
-                if (!disableRtt) {
-                    traceBeginAndSlog("StartWifiRtt");
-                    mSystemServiceManager.startService("com.android.server.wifi.RttService");
-                    traceEnd();
-
-                    if (context.getPackageManager().hasSystemFeature(
-                            PackageManager.FEATURE_WIFI_RTT)) {
-                        traceBeginAndSlog("StartRttService");
-                        mSystemServiceManager.startService(
-                                "com.android.server.wifi.rtt.RttService");
-                        traceEnd();
-                    }
-                }
+            if (!disableRtt) {
+                traceBeginAndSlog("StartWifiRtt");
+                mSystemServiceManager.startService("com.android.server.wifi.RttService");
+                traceEnd();
 
                 if (context.getPackageManager().hasSystemFeature(
-                        PackageManager.FEATURE_WIFI_AWARE)) {
-                    traceBeginAndSlog("StartWifiAware");
-                    mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
+                    PackageManager.FEATURE_WIFI_RTT)) {
+                    traceBeginAndSlog("StartRttService");
+                    mSystemServiceManager.startService(
+                        "com.android.server.wifi.rtt.RttService");
                     traceEnd();
                 }
+            }
 
-                if (context.getPackageManager().hasSystemFeature(
-                        PackageManager.FEATURE_WIFI_DIRECT)) {
-                    traceBeginAndSlog("StartWifiP2P");
-                    mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_AWARE)) {
+                traceBeginAndSlog("StartWifiAware");
+                mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                if (context.getPackageManager().hasSystemFeature(
-                        PackageManager.FEATURE_LOWPAN)) {
-                    traceBeginAndSlog("StartLowpan");
-                    mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_DIRECT)) {
+                traceBeginAndSlog("StartWifiP2P");
+                mSystemServiceManager.startService(WIFI_P2P_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
-                    mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
-                    traceBeginAndSlog("StartEthernet");
-                    mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LOWPAN)) {
+                traceBeginAndSlog("StartLowpan");
+                mSystemServiceManager.startService(LOWPAN_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                traceBeginAndSlog("StartConnectivityService");
-                try {
-                    connectivity = new ConnectivityService(
-                            context, networkManagement, networkStats, networkPolicy);
-                    ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET) ||
+                mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
+                traceBeginAndSlog("StartEthernet");
+                mSystemServiceManager.startService(ETHERNET_SERVICE_CLASS);
+                traceEnd();
+            }
+
+            traceBeginAndSlog("StartConnectivityService");
+            try {
+                connectivity = new ConnectivityService(
+                    context, networkManagement, networkStats, networkPolicy);
+                ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
                             /* allowIsolated= */ false,
-                            DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
-                    networkStats.bindConnectivityManager(connectivity);
-                    networkPolicy.bindConnectivityManager(connectivity);
-                } catch (Throwable e) {
-                    reportWtf("starting Connectivity Service", e);
-                }
-                traceEnd();
-
-                traceBeginAndSlog("StartNsdService");
-                try {
-                    serviceDiscovery = NsdService.create(context);
-                    ServiceManager.addService(
-                            Context.NSD_SERVICE, serviceDiscovery);
-                } catch (Throwable e) {
-                    reportWtf("starting Service Discovery Service", e);
-                }
-                traceEnd();
+                    DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+                networkStats.bindConnectivityManager(connectivity);
+                networkPolicy.bindConnectivityManager(connectivity);
+            } catch (Throwable e) {
+                reportWtf("starting Connectivity Service", e);
             }
+            traceEnd();
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartUpdateLockService");
-                try {
-                    ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
-                            new UpdateLockService(context));
-                } catch (Throwable e) {
-                    reportWtf("starting UpdateLockService", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNsdService");
+            try {
+                serviceDiscovery = NsdService.create(context);
+                ServiceManager.addService(
+                    Context.NSD_SERVICE, serviceDiscovery);
+            } catch (Throwable e) {
+                reportWtf("starting Service Discovery Service", e);
             }
+            traceEnd();
+
+            traceBeginAndSlog("StartUpdateLockService");
+            try {
+                ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
+                    new UpdateLockService(context));
+            } catch (Throwable e) {
+                reportWtf("starting UpdateLockService", e);
+            }
+            traceEnd();
 
             traceBeginAndSlog("StartNotificationManager");
             mSystemServiceManager.startService(NotificationManagerService.class);
@@ -1185,27 +1176,25 @@
             mSystemServiceManager.startService(DeviceStorageMonitorService.class);
             traceEnd();
 
-            if (!disableLocation) {
-                traceBeginAndSlog("StartLocationManagerService");
-                try {
-                    location = new LocationManagerService(context);
-                    ServiceManager.addService(Context.LOCATION_SERVICE, location);
-                } catch (Throwable e) {
-                    reportWtf("starting Location Manager", e);
-                }
-                traceEnd();
-
-                traceBeginAndSlog("StartCountryDetectorService");
-                try {
-                    countryDetector = new CountryDetectorService(context);
-                    ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
-                } catch (Throwable e) {
-                    reportWtf("starting Country Detector", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartLocationManagerService");
+            try {
+                location = new LocationManagerService(context);
+                ServiceManager.addService(Context.LOCATION_SERVICE, location);
+            } catch (Throwable e) {
+                reportWtf("starting Location Manager", e);
             }
+            traceEnd();
 
-            if (!disableNonCoreServices && !disableSearchManager) {
+            traceBeginAndSlog("StartCountryDetectorService");
+            try {
+                countryDetector = new CountryDetectorService(context);
+                ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
+            } catch (Throwable e) {
+                reportWtf("starting Country Detector", e);
+            }
+            traceEnd();
+
+            if (!disableSearchManager) {
                 traceBeginAndSlog("StartSearchManagerService");
                 try {
                     mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS);
@@ -1215,8 +1204,7 @@
                 traceEnd();
             }
 
-            if (!disableNonCoreServices && context.getResources().getBoolean(
-                        R.bool.config_enableWallpaperService)) {
+            if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
                 traceBeginAndSlog("StartWallpaperManagerService");
                 mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
                 traceEnd();
@@ -1232,16 +1220,14 @@
                 traceEnd();
             }
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartDockObserver");
-                mSystemServiceManager.startService(DockObserver.class);
-                traceEnd();
+            traceBeginAndSlog("StartDockObserver");
+            mSystemServiceManager.startService(DockObserver.class);
+            traceEnd();
 
-                if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-                    traceBeginAndSlog("StartThermalObserver");
-                    mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS);
-                    traceEnd();
-                }
+            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+                traceBeginAndSlog("StartThermalObserver");
+                mSystemServiceManager.startService(THERMAL_OBSERVER_CLASS);
+                traceEnd();
             }
 
             traceBeginAndSlog("StartWiredAccessoryManager");
@@ -1254,53 +1240,51 @@
             }
             traceEnd();
 
-            if (!disableNonCoreServices) {
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
-                    // Start MIDI Manager service
-                    traceBeginAndSlog("StartMidiManager");
-                    mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+                // Start MIDI Manager service
+                traceBeginAndSlog("StartMidiManager");
+                mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
-                        || mPackageManager.hasSystemFeature(
-                                PackageManager.FEATURE_USB_ACCESSORY)) {
-                    // Manage USB host and device support
-                    traceBeginAndSlog("StartUsbService");
-                    mSystemServiceManager.startService(USB_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
+                || mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_USB_ACCESSORY)) {
+                // Manage USB host and device support
+                traceBeginAndSlog("StartUsbService");
+                mSystemServiceManager.startService(USB_SERVICE_CLASS);
+                traceEnd();
+            }
 
-                if (!disableSerial) {
-                    traceBeginAndSlog("StartSerialService");
-                    try {
-                        // Serial port support
-                        serial = new SerialService(context);
-                        ServiceManager.addService(Context.SERIAL_SERVICE, serial);
-                    } catch (Throwable e) {
-                        Slog.e(TAG, "Failure starting SerialService", e);
-                    }
-                    traceEnd();
-                }
-
-                traceBeginAndSlog("StartHardwarePropertiesManagerService");
+            if (!disableSerial) {
+                traceBeginAndSlog("StartSerialService");
                 try {
-                    hardwarePropertiesService = new HardwarePropertiesManagerService(context);
-                    ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
-                            hardwarePropertiesService);
+                    // Serial port support
+                    serial = new SerialService(context);
+                    ServiceManager.addService(Context.SERIAL_SERVICE, serial);
                 } catch (Throwable e) {
-                    Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
+                    Slog.e(TAG, "Failure starting SerialService", e);
                 }
                 traceEnd();
             }
 
+            traceBeginAndSlog("StartHardwarePropertiesManagerService");
+            try {
+                hardwarePropertiesService = new HardwarePropertiesManagerService(context);
+                ServiceManager.addService(Context.HARDWARE_PROPERTIES_SERVICE,
+                    hardwarePropertiesService);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting HardwarePropertiesManagerService", e);
+            }
+            traceEnd();
+
             traceBeginAndSlog("StartTwilightService");
             mSystemServiceManager.startService(TwilightService.class);
             traceEnd();
 
-            if (NightDisplayController.isAvailable(context)) {
+            if (ColorDisplayController.isAvailable(context)) {
                 traceBeginAndSlog("StartNightDisplay");
-                mSystemServiceManager.startService(NightDisplayService.class);
+                mSystemServiceManager.startService(ColorDisplayService.class);
                 traceEnd();
             }
 
@@ -1312,48 +1296,46 @@
             mSystemServiceManager.startService(SoundTriggerService.class);
             traceEnd();
 
-            if (!disableNonCoreServices) {
-                if (!disableTrustManager) {
-                    traceBeginAndSlog("StartTrustManager");
-                    mSystemServiceManager.startService(TrustManagerService.class);
-                    traceEnd();
-                }
-
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
-                    traceBeginAndSlog("StartBackupManager");
-                    mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
-                    traceEnd();
-                }
-
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
-                    || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
-                    traceBeginAndSlog("StartAppWidgerService");
-                    mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
-                    traceEnd();
-                }
-
-                // We need to always start this service, regardless of whether the
-                // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
-                // of initializing various settings.  It will internally modify its behavior
-                // based on that feature.
-                traceBeginAndSlog("StartVoiceRecognitionManager");
-                mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
-                traceEnd();
-
-                if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
-                    traceBeginAndSlog("StartGestureLauncher");
-                    mSystemServiceManager.startService(GestureLauncherService.class);
-                    traceEnd();
-                }
-                traceBeginAndSlog("StartSensorNotification");
-                mSystemServiceManager.startService(SensorNotificationService.class);
-                traceEnd();
-
-                traceBeginAndSlog("StartContextHubSystemService");
-                mSystemServiceManager.startService(ContextHubSystemService.class);
+            if (!disableTrustManager) {
+                traceBeginAndSlog("StartTrustManager");
+                mSystemServiceManager.startService(TrustManagerService.class);
                 traceEnd();
             }
 
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
+                traceBeginAndSlog("StartBackupManager");
+                mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
+                traceEnd();
+            }
+
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
+                || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
+                traceBeginAndSlog("StartAppWidgerService");
+                mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
+                traceEnd();
+            }
+
+            // We need to always start this service, regardless of whether the
+            // FEATURE_VOICE_RECOGNIZERS feature is set, because it needs to take care
+            // of initializing various settings.  It will internally modify its behavior
+            // based on that feature.
+            traceBeginAndSlog("StartVoiceRecognitionManager");
+            mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+            traceEnd();
+
+            if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
+                traceBeginAndSlog("StartGestureLauncher");
+                mSystemServiceManager.startService(GestureLauncherService.class);
+                traceEnd();
+            }
+            traceBeginAndSlog("StartSensorNotification");
+            mSystemServiceManager.startService(SensorNotificationService.class);
+            traceEnd();
+
+            traceBeginAndSlog("StartContextHubSystemService");
+            mSystemServiceManager.startService(ContextHubSystemService.class);
+            traceEnd();
+
             traceBeginAndSlog("StartDiskStatsService");
             try {
                 ServiceManager.addService("diskstats", new DiskStatsService(context));
@@ -1375,16 +1357,14 @@
                 traceEnd();
             }
 
-            if (!disableNetwork && !disableNetworkTime) {
-                traceBeginAndSlog("StartNetworkTimeUpdateService");
-                try {
-                    networkTimeUpdater = new NetworkTimeUpdateService(context);
-                    ServiceManager.addService("network_time_update_service", networkTimeUpdater);
-                } catch (Throwable e) {
-                    reportWtf("starting NetworkTimeUpdate service", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartNetworkTimeUpdateService");
+            try {
+                networkTimeUpdater = new NetworkTimeUpdateService(context);
+                ServiceManager.addService("network_time_update_service", networkTimeUpdater);
+            } catch (Throwable e) {
+                reportWtf("starting NetworkTimeUpdate service", e);
             }
+            traceEnd();
 
             traceBeginAndSlog("StartCommonTimeManagementService");
             try {
@@ -1395,38 +1375,32 @@
             }
             traceEnd();
 
-            if (!disableNetwork) {
-                traceBeginAndSlog("CertBlacklister");
-                try {
-                    CertBlacklister blacklister = new CertBlacklister(context);
-                } catch (Throwable e) {
-                    reportWtf("starting CertBlacklister", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("CertBlacklister");
+            try {
+                CertBlacklister blacklister = new CertBlacklister(context);
+            } catch (Throwable e) {
+                reportWtf("starting CertBlacklister", e);
             }
+            traceEnd();
 
-            if (!disableNetwork && !disableNonCoreServices && EmergencyAffordanceManager.ENABLED) {
+            if (EmergencyAffordanceManager.ENABLED) {
                 // EmergencyMode service
                 traceBeginAndSlog("StartEmergencyAffordanceService");
                 mSystemServiceManager.startService(EmergencyAffordanceService.class);
                 traceEnd();
             }
 
-            if (!disableNonCoreServices) {
-                // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
-                traceBeginAndSlog("StartDreamManager");
-                mSystemServiceManager.startService(DreamManagerService.class);
-                traceEnd();
-            }
+            // Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
+            traceBeginAndSlog("StartDreamManager");
+            mSystemServiceManager.startService(DreamManagerService.class);
+            traceEnd();
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("AddGraphicsStatsService");
-                ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
-                        new GraphicsStatsService(context));
-                traceEnd();
-            }
+            traceBeginAndSlog("AddGraphicsStatsService");
+            ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE,
+                new GraphicsStatsService(context));
+            traceEnd();
 
-            if (!disableNonCoreServices && CoverageService.ENABLED) {
+            if (CoverageService.ENABLED) {
                 traceBeginAndSlog("AddCoverageService");
                 ServiceManager.addService(CoverageService.COVERAGE_SERVICE, new CoverageService());
                 traceEnd();
@@ -1477,38 +1451,37 @@
                 traceEnd();
             }
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartMediaRouterService");
-                try {
-                    mediaRouter = new MediaRouterService(context);
-                    ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
-                } catch (Throwable e) {
-                    reportWtf("starting MediaRouterService", e);
-                }
-                traceEnd();
+            traceBeginAndSlog("StartMediaRouterService");
+            try {
+                mediaRouter = new MediaRouterService(context);
+                ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
+            } catch (Throwable e) {
+                reportWtf("starting MediaRouterService", e);
+            }
+            traceEnd();
 
-                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
-                    traceBeginAndSlog("StartFingerprintSensor");
-                    mSystemServiceManager.startService(FingerprintService.class);
-                    traceEnd();
-                }
-
-                traceBeginAndSlog("StartBackgroundDexOptService");
-                try {
-                    BackgroundDexOptService.schedule(context);
-                } catch (Throwable e) {
-                    reportWtf("starting StartBackgroundDexOptService", e);
-                }
-                traceEnd();
-
-                traceBeginAndSlog("StartPruneInstantAppsJobService");
-                try {
-                    PruneInstantAppsJobService.schedule(context);
-                } catch (Throwable e) {
-                    reportWtf("StartPruneInstantAppsJobService", e);
-                }
+            if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+                traceBeginAndSlog("StartFingerprintSensor");
+                mSystemServiceManager.startService(FingerprintService.class);
                 traceEnd();
             }
+
+            traceBeginAndSlog("StartBackgroundDexOptService");
+            try {
+                BackgroundDexOptService.schedule(context);
+            } catch (Throwable e) {
+                reportWtf("starting StartBackgroundDexOptService", e);
+            }
+            traceEnd();
+
+            traceBeginAndSlog("StartPruneInstantAppsJobService");
+            try {
+                PruneInstantAppsJobService.schedule(context);
+            } catch (Throwable e) {
+                reportWtf("StartPruneInstantAppsJobService", e);
+            }
+            traceEnd();
+
             // LauncherAppsService uses ShortcutService.
             traceBeginAndSlog("StartShortcutServiceLifecycle");
             mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
@@ -1517,9 +1490,13 @@
             traceBeginAndSlog("StartLauncherAppsService");
             mSystemServiceManager.startService(LauncherAppsService.class);
             traceEnd();
+
+            traceBeginAndSlog("StartCrossProfileAppsService");
+            mSystemServiceManager.startService(CrossProfileAppsService.class);
+            traceEnd();
         }
 
-        if (!disableNonCoreServices && !disableMediaProjection) {
+        if (!disableMediaProjection) {
             traceBeginAndSlog("StartMediaProjectionManager");
             mSystemServiceManager.startService(MediaProjectionManagerService.class);
             traceEnd();
@@ -1530,17 +1507,15 @@
             mSystemServiceManager.startService(WEAR_CONNECTIVITY_SERVICE_CLASS);
             traceEnd();
 
-            if (!disableNonCoreServices) {
-                traceBeginAndSlog("StartWearTimeService");
-                mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
-                mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
-                traceEnd();
+            traceBeginAndSlog("StartWearTimeService");
+            mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
+            mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
+            traceEnd();
 
-                if (enableLeftyService) {
-                    traceBeginAndSlog("StartWearLeftyService");
-                    mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
-                    traceEnd();
-                }
+            if (enableLeftyService) {
+                traceBeginAndSlog("StartWearLeftyService");
+                mSystemServiceManager.startService(WEAR_LEFTY_SERVICE_CLASS);
+                traceEnd();
             }
         }
 
diff --git a/com/android/server/TelephonyRegistry.java b/com/android/server/TelephonyRegistry.java
index e609bd7..831c9cb 100644
--- a/com/android/server/TelephonyRegistry.java
+++ b/com/android/server/TelephonyRegistry.java
@@ -1356,31 +1356,6 @@
         }
     }
 
-    public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) {
-        if (!checkNotifyPermission("notifyOemHookRawEventForSubscriber")) {
-            return;
-        }
-
-        synchronized (mRecords) {
-            for (Record r : mRecords) {
-                if (VDBG) {
-                    log("notifyOemHookRawEventForSubscriber:  r=" + r + " subId=" + subId);
-                }
-                if ((r.matchPhoneStateListenerEvent(
-                        PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT)) &&
-                        ((r.subId == subId) ||
-                        (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID))) {
-                    try {
-                        r.callback.onOemHookRawEvent(rawData);
-                    } catch (RemoteException ex) {
-                        mRemoveList.add(r.binder);
-                    }
-                }
-            }
-            handleRemoveListLocked();
-        }
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -1673,11 +1648,6 @@
                     android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
 
         }
-
-        if ((events & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
-        }
     }
 
     private void handleRemoveListLocked() {
diff --git a/com/android/server/VibratorService.java b/com/android/server/VibratorService.java
index 8b79b9d..0e51fda 100644
--- a/com/android/server/VibratorService.java
+++ b/com/android/server/VibratorService.java
@@ -27,6 +27,7 @@
 import android.hardware.input.InputManager;
 import android.hardware.vibrator.V1_0.Constants.EffectStrength;
 import android.media.AudioManager;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerSaveState;
 import android.os.BatteryStats;
 import android.os.Handler;
@@ -56,7 +57,6 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/com/android/server/Watchdog.java b/com/android/server/Watchdog.java
index 8d46d1e..35f83e4 100644
--- a/com/android/server/Watchdog.java
+++ b/com/android/server/Watchdog.java
@@ -60,9 +60,6 @@
     // Set this to true to use debug default values.
     static final boolean DB = false;
 
-    // Set this to true to have the watchdog record kernel thread stacks when it fires
-    static final boolean RECORD_KERNEL_THREADS = true;
-
     // Note 1: Do not lower this value below thirty seconds without tightening the invoke-with
     //         timeout in com.android.internal.os.ZygoteConnection, or wrapped applications
     //         can trigger the watchdog.
@@ -509,11 +506,6 @@
             // The system's been hanging for a minute, another second or two won't hurt much.
             SystemClock.sleep(2000);
 
-            // Pull our own kernel thread stacks as well if we're configured for that
-            if (RECORD_KERNEL_THREADS) {
-                dumpKernelStackTraces();
-            }
-
             // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
             doSysRq('w');
             doSysRq('l');
@@ -591,18 +583,6 @@
         }
     }
 
-    private File dumpKernelStackTraces() {
-        String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
-        if (tracesPath == null || tracesPath.length() == 0) {
-            return null;
-        }
-
-        native_dumpKernelStacks(tracesPath);
-        return new File(tracesPath);
-    }
-
-    private native void native_dumpKernelStacks(String tracesPath);
-
     public static final class OpenFdMonitor {
         /**
          * Number of FDs below the soft limit that we trigger a runtime restart at. This was
diff --git a/com/android/server/accessibility/AccessibilityManagerService.java b/com/android/server/accessibility/AccessibilityManagerService.java
index 06c110d..d661754 100644
--- a/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1318,7 +1318,7 @@
 
     private void unbindAllServicesLocked(UserState userState) {
         List<AccessibilityServiceConnection> services = userState.mBoundServices;
-        for (int count = services.size(); count > 1; count--) {
+        for (int count = services.size(); count > 0; count--) {
             // When the service is unbound, it disappears from the list, so there's no need to
             // keep track of the index
             services.get(0).unbindLocked();
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
index c04ddf8..2289f85 100644
--- a/com/android/server/am/ActivityDisplay.java
+++ b/com/android/server/am/ActivityDisplay.java
@@ -276,17 +276,8 @@
         if (windowingMode == WINDOWING_MODE_PINNED) {
             return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
         }
-        final T stack = (T) new ActivityStack(
+        return (T) new ActivityStack(
                         this, stackId, mSupervisor, windowingMode, activityType, onTop);
-
-        if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            // Make sure recents stack exist when creating a dock stack as it normally needs to be
-            // on the other side of the docked stack and we make visibility decisions based on that.
-            // TODO: Not sure if this is needed after we change to calculate visibility based on
-            // stack z-order vs. id.
-            getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
-        }
-        return stack;
     }
 
     /**
@@ -365,6 +356,7 @@
                         + " already exist on display=" + this + " stack=" + stack);
             }
             mSplitScreenPrimaryStack = stack;
+            onSplitScreenModeActivated();
         }
     }
 
@@ -377,6 +369,42 @@
             mPinnedStack = null;
         } else if (stack == mSplitScreenPrimaryStack) {
             mSplitScreenPrimaryStack = null;
+            // Inform the reset of the system that split-screen mode was dismissed so things like
+            // resizing all the other stacks can take place.
+            onSplitScreenModeDismissed();
+        }
+    }
+
+    private void onSplitScreenModeDismissed() {
+        mSupervisor.mWindowManager.deferSurfaceLayout();
+        try {
+            // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
+            for (int i = mStacks.size() - 1; i >= 0; --i) {
+                final ActivityStack otherStack = mStacks.get(i);
+                if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
+                    continue;
+                }
+                otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+            }
+        } finally {
+            mSupervisor.mWindowManager.continueSurfaceLayout();
+        }
+    }
+
+    private void onSplitScreenModeActivated() {
+        mSupervisor.mWindowManager.deferSurfaceLayout();
+        try {
+            // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
+            for (int i = mStacks.size() - 1; i >= 0; --i) {
+                final ActivityStack otherStack = mStacks.get(i);
+                if (otherStack == mSplitScreenPrimaryStack
+                        || !otherStack.affectedBySplitScreenResize()) {
+                    continue;
+                }
+                otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+            }
+        } finally {
+            mSupervisor.mWindowManager.continueSurfaceLayout();
         }
     }
 
@@ -404,8 +432,8 @@
 
         if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-            return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
-                    windowingMode, activityType);
+            return supportsSplitScreen
+                    && WindowConfiguration.supportSplitScreenWindowingMode(activityType);
         }
 
         if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
@@ -475,22 +503,10 @@
                 supportsFreeform, supportsPip, activityType)) {
             return windowingMode;
         }
-        // Return the display's windowing mode
-        return getWindowingMode();
-    }
-
-    /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */
-    int getTopVisibleStackActivityType(int excludeWindowingMode) {
-        for (int i = mStacks.size() - 1; i >= 0; --i) {
-            final ActivityStack stack = mStacks.get(i);
-            if (stack.getWindowingMode() == excludeWindowingMode) {
-                continue;
-            }
-            if (stack.shouldBeVisible(null /* starting */)) {
-                return stack.getActivityType();
-            }
-        }
-        return ACTIVITY_TYPE_UNDEFINED;
+        // Try to use the display's windowing mode otherwise fallback to fullscreen.
+        windowingMode = getWindowingMode();
+        return windowingMode != WINDOWING_MODE_UNDEFINED
+                ? windowingMode : WINDOWING_MODE_FULLSCREEN;
     }
 
     /**
@@ -599,7 +615,20 @@
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + "displayId=" + mDisplayId + " mStacks=" + mStacks);
+        pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size());
+        final String myPrefix = prefix + " ";
+        if (mHomeStack != null) {
+            pw.println(myPrefix + "mHomeStack=" + mHomeStack);
+        }
+        if (mRecentsStack != null) {
+            pw.println(myPrefix + "mRecentsStack=" + mRecentsStack);
+        }
+        if (mPinnedStack != null) {
+            pw.println(myPrefix + "mPinnedStack=" + mPinnedStack);
+        }
+        if (mSplitScreenPrimaryStack != null) {
+            pw.println(myPrefix + "mSplitScreenPrimaryStack=" + mSplitScreenPrimaryStack);
+        }
     }
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/com/android/server/am/ActivityManagerDebugConfig.java b/com/android/server/am/ActivityManagerDebugConfig.java
index ceb2ad6..0a7d3fd 100644
--- a/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/com/android/server/am/ActivityManagerDebugConfig.java
@@ -92,6 +92,7 @@
     static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false;
     static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false;
     static final boolean DEBUG_WHITELISTS = DEBUG_ALL || false;
+    static final boolean DEBUG_METRICS = DEBUG_ALL || false;
 
     static final String POSTFIX_ADD_REMOVE = (APPEND_CATEGORY_NAME) ? "_AddRemove" : "";
     static final String POSTFIX_APP = (APPEND_CATEGORY_NAME) ? "_App" : "";
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index f2e0493..4361856 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.BIND_VOICE_INTERACTION;
 import static android.Manifest.permission.CHANGE_CONFIGURATION;
 import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
@@ -25,10 +26,16 @@
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REMOVE_TASKS;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-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.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_CONTENT;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.app.AppOpsManager.OP_NONE;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -37,6 +44,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
@@ -214,6 +222,7 @@
 import android.app.IActivityController;
 import android.app.IActivityManager;
 import android.app.IApplicationThread;
+import android.app.IAssistDataReceiver;
 import android.app.IInstrumentationWatcher;
 import android.app.INotificationManager;
 import android.app.IProcessObserver;
@@ -331,7 +340,6 @@
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionManagerInternal;
-import android.service.voice.VoiceInteractionSession;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -393,6 +401,7 @@
 import com.android.server.AttributeCache;
 import com.android.server.DeviceIdleController;
 import com.android.server.IntentResolver;
+import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.NetworkManagementInternal;
@@ -782,7 +791,7 @@
         public final Bundle extras;
         public final Intent intent;
         public final String hint;
-        public final IResultReceiver receiver;
+        public final IAssistDataReceiver receiver;
         public final int userHandle;
         public boolean haveResult = false;
         public Bundle result = null;
@@ -791,7 +800,8 @@
         public Bundle receiverExtras;
 
         public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
-                String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
+                String _hint, IAssistDataReceiver _receiver, Bundle _receiverExtras,
+                int _userHandle) {
             activity = _activity;
             extras = _extras;
             intent = _intent;
@@ -812,8 +822,7 @@
         }
     }
 
-    final ArrayList<PendingAssistExtras> mPendingAssistExtras
-            = new ArrayList<PendingAssistExtras>();
+    final ArrayList<PendingAssistExtras> mPendingAssistExtras = new ArrayList<>();
 
     /**
      * Process management.
@@ -1003,15 +1012,6 @@
     private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
 
     /**
-     * Strict Mode background batched logging state.
-     *
-     * The string buffer is guarded by itself, and its lock is also
-     * used to determine if another batched write is already
-     * in-flight.
-     */
-    private final StringBuilder mStrictModeBuffer = new StringBuilder();
-
-    /**
      * Keeps track of all IIntentReceivers that have been registered for broadcasts.
      * Hash keys are the receiver IBinder, hash value is a ReceiverList.
      */
@@ -2779,12 +2779,12 @@
         mConfigurationSeq = mTempConfig.seq = 1;
         mStackSupervisor = createStackSupervisor();
         mStackSupervisor.onConfigurationChanged(mTempConfig);
-        mKeyguardController = mStackSupervisor.mKeyguardController;
+        mKeyguardController = mStackSupervisor.getKeyguardController();
         mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
         mTaskChangeNotificationController =
                 new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
-        mActivityStarter = new ActivityStarter(this);
+        mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
         mRecentTasks = createRecentTasks();
         mStackSupervisor.setRecentTasks(mRecentTasks);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
@@ -2828,7 +2828,9 @@
     }
 
     protected ActivityStackSupervisor createStackSupervisor() {
-        return new ActivityStackSupervisor(this, mHandler.getLooper());
+        final ActivityStackSupervisor supervisor = new ActivityStackSupervisor(this, mHandler.getLooper());
+        supervisor.initialize();
+        return supervisor;
     }
 
     protected RecentTasks createRecentTasks() {
@@ -3061,7 +3063,7 @@
     public void batterySendBroadcast(Intent intent) {
         synchronized (this) {
             broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
-                    AppOpsManager.OP_NONE, null, false, false,
+                    OP_NONE, null, false, false,
                     -1, SYSTEM_UID, UserHandle.USER_ALL);
         }
     }
@@ -3840,6 +3842,10 @@
                 gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
                 gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
                 gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
+
+                // Replace any invalid GIDs
+                if (gids[0] == UserHandle.ERR_GID) gids[0] = gids[2];
+                if (gids[1] == UserHandle.ERR_GID) gids[1] = gids[2];
             }
             checkTime(startTime, "startProcess: building args");
             if (mFactoryTest != FactoryTest.FACTORY_TEST_OFF) {
@@ -3889,7 +3895,7 @@
             }
 
             if (app.info.isPrivilegedApp() &&
-                    !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+                    SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
                 runtimeFlags |= Zygote.DISABLE_VERIFIER;
                 runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
             }
@@ -4035,10 +4041,14 @@
         if (DEBUG_SWITCH) Slog.d(TAG_SWITCH,
                 "updateUsageStats: comp=" + component + "res=" + resumed);
         final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+        StatsLog.write(StatsLog.ACTIVITY_FOREGROUND_STATE_CHANGED,
+            component.userId, component.realActivity.getPackageName(),
+            component.realActivity.getShortClassName(), resumed ? 1 : 0);
         if (resumed) {
             if (mUsageStatsService != null) {
                 mUsageStatsService.reportEvent(component.realActivity, component.userId,
                         UsageEvents.Event.MOVE_TO_FOREGROUND);
+
             }
             synchronized (stats) {
                 stats.noteActivityResumedLocked(component.app.uid);
@@ -4083,7 +4093,7 @@
             ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                     aInfo.applicationInfo.uid, true);
             if (app == null || app.instr == null) {
-                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+                intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
                 final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
                 // For ANR debugging to verify if the user activity is the one that actually
                 // launched.
@@ -4155,7 +4165,7 @@
                 String lastVers = Settings.Secure.getString(
                         resolver, Settings.Secure.LAST_SETUP_SHOWN);
                 if (vers != null && !vers.equals(lastVers)) {
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
                     intent.setComponent(new ComponentName(
                             ri.activityInfo.packageName, ri.activityInfo.name));
                     mActivityStarter.startActivityLocked(null, intent, null /*ephemeralIntent*/,
@@ -4678,15 +4688,7 @@
             Intent intent, String resolvedType, IVoiceInteractionSession session,
             IVoiceInteractor interactor, int startFlags, ProfilerInfo profilerInfo,
             Bundle bOptions, int userId) {
-        if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: startVoiceActivity() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
+        enforceCallingPermission(BIND_VOICE_INTERACTION, "startVoiceActivity()");
         if (session == null || interactor == null) {
             throw new NullPointerException("null session or interactor");
         }
@@ -4701,15 +4703,7 @@
     @Override
     public int startAssistantActivity(String callingPackage, int callingPid, int callingUid,
             Intent intent, String resolvedType, Bundle bOptions, int userId) {
-        if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
-                != PackageManager.PERMISSION_GRANTED) {
-            final String msg = "Permission Denial: startAssistantActivity() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + Manifest.permission.BIND_VOICE_INTERACTION;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
+        enforceCallingPermission(BIND_VOICE_INTERACTION, "startAssistantActivity()");
         userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
                 ALLOW_FULL_ONLY, "startAssistantActivity", null);
         return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent,
@@ -4718,11 +4712,57 @@
     }
 
     @Override
+    public int startRecentsActivity(IAssistDataReceiver assistDataReceiver, Bundle options,
+            Bundle activityOptions, int userId) {
+        if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
+            String msg = "Permission Denial: startRecentsActivity() from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " not recent tasks package";
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        final int recentsUid = mRecentTasks.getRecentsComponentUid();
+        final ComponentName recentsComponent = mRecentTasks.getRecentsComponent();
+        final String recentsPackage = recentsComponent.getPackageName();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                // If provided, kick off the request for the assist data in the background before
+                // starting the activity
+                if (assistDataReceiver != null) {
+                    final AppOpsManager appOpsManager = (AppOpsManager)
+                            mContext.getSystemService(Context.APP_OPS_SERVICE);
+                    final AssistDataReceiverProxy proxy = new AssistDataReceiverProxy(
+                            assistDataReceiver, recentsPackage);
+                    final AssistDataRequester requester = new AssistDataRequester(mContext, this,
+                            mWindowManager, appOpsManager, proxy, this,
+                            OP_ASSIST_STRUCTURE, OP_NONE);
+                    requester.requestAssistData(mStackSupervisor.getTopVisibleActivities(),
+                            true /* fetchData */, false /* fetchScreenshots */,
+                            true /* allowFetchData */, false /* alloweFetchScreenshots */,
+                            recentsUid, recentsPackage);
+                }
+
+                final Intent intent = new Intent();
+                intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+                intent.setComponent(recentsComponent);
+                intent.putExtras(options);
+                return mActivityStarter.startActivityMayWait(null, recentsUid, recentsPackage,
+                        intent, null, null, null, null, null, 0, 0, null, null, null, activityOptions,
+                        false, userId, null, "startRecentsActivity");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
     public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options)
             throws RemoteException {
         Slog.i(TAG, "Activity tried to startVoiceInteraction");
         synchronized (this) {
-            ActivityRecord activity = getFocusedStack().topActivity();
+            ActivityRecord activity = getFocusedStack().getTopActivity();
             if (ActivityRecord.forTokenLocked(callingActivity) != activity) {
                 throw new SecurityException("Only focused activity can call startVoiceInteraction");
             }
@@ -4863,7 +4903,7 @@
                     Intent.FLAG_ACTIVITY_FORWARD_RESULT|
                     Intent.FLAG_ACTIVITY_CLEAR_TOP|
                     Intent.FLAG_ACTIVITY_MULTIPLE_TASK|
-                    Intent.FLAG_ACTIVITY_NEW_TASK));
+                    FLAG_ACTIVITY_NEW_TASK));
 
             // Okay now we need to start the new activity, replacing the
             // currently running activity.  This is a little tricky because
@@ -5317,8 +5357,7 @@
         return -1;
     }
 
-    final ProcessRecord getRecordForAppLocked(
-            IApplicationThread thread) {
+    ProcessRecord getRecordForAppLocked(IApplicationThread thread) {
         if (thread == null) {
             return null;
         }
@@ -6292,7 +6331,7 @@
         mStackSupervisor.closeSystemDialogsLocked();
 
         broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
-                AppOpsManager.OP_NONE, null, false, false,
+                OP_NONE, null, false, false,
                 -1, SYSTEM_UID, UserHandle.USER_ALL);
     }
 
@@ -6394,7 +6433,7 @@
         intent.putExtra(Intent.EXTRA_UID, uid);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
         broadcastIntentLocked(null, null, intent,
-                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                null, null, 0, null, null, null, OP_NONE,
                 null, false, false, MY_PID, SYSTEM_UID, UserHandle.getUserId(uid));
     }
 
@@ -7042,10 +7081,12 @@
             }
 
             // We deprecated Build.SERIAL and it is not accessible to
-            // apps that target the v2 security sandbox. Since access to
-            // the serial is now behind a permission we push down the value.
-            String buildSerial = appInfo.targetSandboxVersion < 2
-                    ? sTheRealBuildSerial : Build.UNKNOWN;
+            // apps that target the v2 security sandbox and to apps that
+            // target APIs higher than O MR1. Since access to the serial
+            // is now behind a permission we push down the value.
+            final String buildSerial = (appInfo.targetSandboxVersion < 2
+                    && appInfo.targetSdkVersion <= Build.VERSION_CODES.O_MR1)
+                            ? sTheRealBuildSerial : Build.UNKNOWN;
 
             // Check if this is a secondary process that should be incorporated into some
             // currently active instrumentation.  (Note we do this AFTER all of the profiling
@@ -7082,7 +7123,7 @@
             }
 
             checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
-            mStackSupervisor.mActivityMetricsLogger.notifyBindApplication(app);
+            mStackSupervisor.getActivityMetricsLogger().notifyBindApplication(app);
             if (app.isolatedEntryPoint != null) {
                 // This is an isolated process which should just call an entry point instead of
                 // being bound to an application.
@@ -10396,13 +10437,7 @@
                             "exitFreeformMode: You can only go fullscreen from freeform.");
                 }
 
-                final ActivityStack fullscreenStack = stack.getDisplay().getOrCreateStack(
-                        WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
-
-                if (DEBUG_STACK) Slog.d(TAG_STACK, "exitFreeformMode: " + r);
-                // TODO: Should just change windowing mode vs. re-parenting...
-                r.getTask().reparent(fullscreenStack, ON_TOP,
-                        REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "exitFreeformMode");
+                stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -10411,6 +10446,11 @@
 
     @Override
     public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
+                    toTop, ANIMATE, null /* initialBounds */);
+            return;
+        }
         enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
@@ -10423,20 +10463,16 @@
 
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "setTaskWindowingMode: moving task=" + taskId
                         + " to windowingMode=" + windowingMode + " toTop=" + toTop);
-                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                    mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
-                            null /* initialBounds */);
-                }
 
                 if (!task.isActivityTypeStandardOrUndefined()) {
-                    throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move task "
-                            + taskId + " to non-standard windowin mode=" + windowingMode);
+                    throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
+                            + " non-standard task " + taskId + " to windowing mode="
+                            + windowingMode);
                 }
                 final ActivityDisplay display = task.getStack().getDisplay();
                 final ActivityStack stack = display.getOrCreateStack(windowingMode,
                         task.getStack().getActivityType(), toTop);
-                // TODO: We should just change the windowing mode for the task vs. creating and
-                // moving it to a stack.
+                // TODO: Use ActivityStack.setWindowingMode instead of re-parenting.
                 task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
                         "moveTaskToStack");
             } finally {
@@ -10445,6 +10481,55 @@
         }
     }
 
+    /**
+     * Moves the specified 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
+     *                      stack. Pass {@code null} to use default bounds.
+     */
+    @Override
+    public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+            boolean animate, Rect initialBounds) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "setTaskWindowingModeSplitScreenPrimary()");
+        synchronized (this) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+                if (task == null) {
+                    Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);
+                    return false;
+                }
+                if (DEBUG_STACK) Slog.d(TAG_STACK,
+                        "setTaskWindowingModeSplitScreenPrimary: moving task=" + taskId
+                        + " to createMode=" + createMode + " toTop=" + toTop);
+                if (!task.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move"
+                            + " non-standard task " + taskId + " to split-screen windowing mode");
+                }
+
+                mWindowManager.setDockedStackCreateState(createMode, initialBounds);
+                final int windowingMode = task.getWindowingMode();
+                final ActivityStack stack = task.getStack();
+                if (toTop) {
+                    stack.moveToFront("setTaskWindowingModeSplitScreenPrimary", task);
+                }
+                stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate);
+                return windowingMode != task.getWindowingMode();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
     @Override
     public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
         enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
@@ -10471,7 +10556,7 @@
                 }
                 if (stack.inSplitScreenPrimaryWindowingMode()) {
                     mWindowManager.setDockedStackCreateState(
-                            DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
+                            SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
                 }
                 task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
                         "moveTaskToStack");
@@ -10482,56 +10567,6 @@
     }
 
     /**
-     * Moves the input task to the docked stack.
-     *
-     * @param taskId Id of task to move.
-     * @param createMode The mode the docked stack should be created in if it doesn't exist
-     *                   already. See
-     *                   {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT}
-     *                   and
-     *                   {@link android.app.ActivityManager#DOCKED_STACK_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 docked stack gets created, it will use these bounds for the
-     *                      docked stack. Pass {@code null} to use default bounds.
-     */
-    @Override
-    public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
-            Rect initialBounds) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
-        synchronized (this) {
-            long ident = Binder.clearCallingIdentity();
-            try {
-                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
-                if (task == null) {
-                    Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
-                    return false;
-                }
-                if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
-                        + " to createMode=" + createMode + " toTop=" + toTop);
-                mWindowManager.setDockedStackCreateState(createMode, initialBounds);
-
-                final ActivityDisplay display = task.getStack().getDisplay();
-                final ActivityStack stack = display.getOrCreateStack(
-                        WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, task.getStack().getActivityType(),
-                        toTop);
-
-                // Defer resuming until we move the home stack to the front below
-                // TODO: Should just change windowing mode vs. re-parenting...
-                final boolean moved = task.reparent(stack, toTop,
-                        REPARENT_KEEP_STACK_AT_FRONT, animate, !DEFER_RESUME,
-                        "moveTaskToDockedStack");
-                if (moved) {
-                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
-                }
-                return moved;
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-
-    /**
      * Dismisses split-screen multi-window mode.
      * @param toTop If true the current primary split-screen stack will be placed or left on top.
      */
@@ -10547,14 +10582,11 @@
                     Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found.");
                     return;
                 }
+
                 if (toTop) {
-                    mStackSupervisor.resizeStackLocked(stack, null /* destBounds */,
-                            null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
-                            true /* preserveWindows */, true /* allowResizeInDockedMode */,
-                            !DEFER_RESUME);
-                } else {
-                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, false /* onTop */);
+                    stack.moveToFront("dismissSplitScreenMode");
                 }
+                stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -10806,7 +10838,7 @@
         }
     }
 
-    private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isAppPinning) {
+    private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isSystemCaller) {
         if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task);
         if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
             return;
@@ -10820,13 +10852,16 @@
         // When a task is locked, dismiss the pinned stack if it exists
         mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
 
-        // isAppPinning is used to distinguish between locked and pinned mode, as pinned mode
-        // is initiated by system after the pinning request was shown and locked mode is initiated
-        // by an authorized app directly
+        // {@code isSystemCaller} is used to distinguish whether this request is initiated by the
+        // system or a specific app.
+        // * System-initiated requests will only start the pinned mode (screen pinning)
+        // * App-initiated requests
+        //   - will put the device in fully locked mode (LockTask), if the app is whitelisted
+        //   - will start the pinned mode, otherwise
         final int callingUid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
-            mLockTaskController.startLockTaskMode(task, isAppPinning, callingUid);
+            mLockTaskController.startLockTaskMode(task, isSystemCaller, callingUid);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -10839,7 +10874,7 @@
             if (r == null) {
                 return;
             }
-            startLockTaskModeLocked(r.getTask(), false /* not system initiated */);
+            startLockTaskModeLocked(r.getTask(), false /* isSystemCaller */);
         }
     }
 
@@ -10851,7 +10886,7 @@
         try {
             synchronized (this) {
                 startLockTaskModeLocked(mStackSupervisor.anyTaskForIdLocked(taskId),
-                        true /* system initiated */);
+                        true /* isSystemCaller */);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -10859,8 +10894,14 @@
     }
 
     @Override
-    public void stopLockTaskMode() {
-        stopLockTaskModeInternal(false /* not system initiated */);
+    public void stopLockTaskModeByToken(IBinder token) {
+        synchronized (this) {
+            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+            if (r == null) {
+                return;
+            }
+            stopLockTaskModeInternal(r.getTask(), false /* isSystemCaller */);
+        }
     }
 
     /**
@@ -10870,15 +10911,15 @@
     @Override
     public void stopSystemLockTaskMode() throws RemoteException {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "stopSystemLockTaskMode");
-        stopLockTaskModeInternal(true /* system initiated */);
+        stopLockTaskModeInternal(null, true /* isSystemCaller */);
     }
 
-    private void stopLockTaskModeInternal(boolean isSystemRequest) {
+    private void stopLockTaskModeInternal(@Nullable TaskRecord task, boolean isSystemCaller) {
         final int callingUid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                mLockTaskController.stopLockTaskMode(isSystemRequest, callingUid);
+                mLockTaskController.stopLockTaskMode(task, isSystemCaller, callingUid);
             }
             // Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
             // task and jumping straight into a call in the case of emergency call back.
@@ -11629,7 +11670,7 @@
             }
 
             final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+            intent.addFlags(FLAG_ACTIVITY_NEW_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, cpi.packageName);
 
@@ -12901,7 +12942,7 @@
                 return false;
             }
 
-            final ActivityRecord activity = focusedStack.topActivity();
+            final ActivityRecord activity = focusedStack.getTopActivity();
             if (activity == null) {
                 return false;
             }
@@ -12918,7 +12959,7 @@
         try {
             synchronized (this) {
                 ActivityRecord caller = ActivityRecord.forTokenLocked(token);
-                ActivityRecord top = getFocusedStack().topActivity();
+                ActivityRecord top = getFocusedStack().getTopActivity();
                 if (top != caller) {
                     Slog.w(TAG, "showAssistFromActivity failed: caller " + caller
                             + " is not current top " + top);
@@ -12938,7 +12979,7 @@
     }
 
     @Override
-    public boolean requestAssistContextExtras(int requestType, IResultReceiver receiver,
+    public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
             Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
         return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
                 activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
@@ -12946,7 +12987,7 @@
     }
 
     @Override
-    public boolean requestAutofillData(IResultReceiver receiver, Bundle receiverExtras,
+    public boolean requestAutofillData(IAssistDataReceiver receiver, Bundle receiverExtras,
             IBinder activityToken, int flags) {
         return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null,
                 receiver, receiverExtras, activityToken, true, true, UserHandle.getCallingUserId(),
@@ -12954,14 +12995,14 @@
     }
 
     private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
-            IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
+            IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
             boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
             int flags) {
         enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
                 "enqueueAssistContext()");
 
         synchronized (this) {
-            ActivityRecord activity = getFocusedStack().topActivity();
+            ActivityRecord activity = getFocusedStack().getTopActivity();
             if (activity == null) {
                 Slog.w(TAG, "getAssistContextExtras failed: no top activity");
                 return null;
@@ -13022,7 +13063,7 @@
     }
 
     void pendingAssistExtrasTimedOut(PendingAssistExtras pae) {
-        IResultReceiver receiver;
+        IAssistDataReceiver receiver;
         synchronized (this) {
             mPendingAssistExtras.remove(pae);
             receiver = pae.receiver;
@@ -13031,10 +13072,9 @@
             // Caller wants result sent back to them.
             Bundle sendBundle = new Bundle();
             // At least return the receiver extras
-            sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
-                    pae.receiverExtras);
+            sendBundle.putBundle(ASSIST_KEY_RECEIVER_EXTRAS, pae.receiverExtras);
             try {
-                pae.receiver.send(0, sendBundle);
+                pae.receiver.onHandleAssistData(sendBundle);
             } catch (RemoteException e) {
             }
         }
@@ -13072,7 +13112,7 @@
             }
         }
         // We are now ready to launch the assist activity.
-        IResultReceiver sendReceiver = null;
+        IAssistDataReceiver sendReceiver = null;
         Bundle sendBundle = null;
         synchronized (this) {
             buildAssistBundleLocked(pae, extras);
@@ -13085,16 +13125,15 @@
             if ((sendReceiver=pae.receiver) != null) {
                 // Caller wants result sent back to them.
                 sendBundle = new Bundle();
-                sendBundle.putBundle(VoiceInteractionSession.KEY_DATA, pae.extras);
-                sendBundle.putParcelable(VoiceInteractionSession.KEY_STRUCTURE, pae.structure);
-                sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content);
-                sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
-                        pae.receiverExtras);
+                sendBundle.putBundle(ASSIST_KEY_DATA, pae.extras);
+                sendBundle.putParcelable(ASSIST_KEY_STRUCTURE, pae.structure);
+                sendBundle.putParcelable(ASSIST_KEY_CONTENT, pae.content);
+                sendBundle.putBundle(ASSIST_KEY_RECEIVER_EXTRAS, pae.receiverExtras);
             }
         }
         if (sendReceiver != null) {
             try {
-                sendReceiver.send(0, sendBundle);
+                sendReceiver.onHandleAssistData(sendBundle);
             } catch (RemoteException e) {
             }
             return;
@@ -13108,7 +13147,7 @@
                 mContext.startServiceAsUser(pae.intent, new UserHandle(pae.userHandle));
             } else {
                 pae.intent.replaceExtras(pae.extras);
-                pae.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                pae.intent.setFlags(FLAG_ACTIVITY_NEW_TASK
                         | Intent.FLAG_ACTIVITY_SINGLE_TOP
                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                 closeSystemDialogs("assist");
@@ -13875,7 +13914,7 @@
                     .setPackage("android")
                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             broadcastIntent(null, intent, null, null, 0, null, null, null,
-                    android.app.AppOpsManager.OP_NONE, null, true, false, UserHandle.USER_ALL);
+                    OP_NONE, null, true, false, UserHandle.USER_ALL);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -14129,7 +14168,7 @@
                         | Intent.FLAG_RECEIVER_FOREGROUND);
                 intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
                 broadcastIntentLocked(null, null, intent,
-                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, null, 0, null, null, null, OP_NONE,
                         null, false, false, MY_PID, SYSTEM_UID,
                         currentUserId);
                 intent = new Intent(Intent.ACTION_USER_STARTING);
@@ -14143,7 +14182,7 @@
                                     throws RemoteException {
                             }
                         }, 0, null, null,
-                        new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                        new String[] {INTERACT_ACROSS_USERS}, OP_NONE,
                         null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
             } catch (Throwable t) {
                 Slog.wtf(TAG, "Failed sending first user broadcasts", t);
@@ -14223,10 +14262,9 @@
             IBinder app,
             int violationMask,
             StrictMode.ViolationInfo info) {
-        ProcessRecord r = findAppProcess(app, "StrictMode");
-        if (r == null) {
-            return;
-        }
+        // We're okay if the ProcessRecord is missing; it probably means that
+        // we're reporting a violation from the system process itself.
+        final ProcessRecord r = findAppProcess(app, "StrictMode");
 
         if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) {
             Integer stackFingerprint = info.hashCode();
@@ -14288,18 +14326,15 @@
                 (process.info.flags & (ApplicationInfo.FLAG_SYSTEM |
                                        ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0;
         final String processName = process == null ? "unknown" : process.processName;
-        final String dropboxTag = isSystemApp ? "system_app_strictmode" : "data_app_strictmode";
         final DropBoxManager dbox = (DropBoxManager)
                 mContext.getSystemService(Context.DROPBOX_SERVICE);
 
         // Exit early if the dropbox isn't configured to accept this report type.
+        final String dropboxTag = processClass(process) + "_strictmode";
         if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
 
-        boolean bufferWasEmpty;
-        boolean needsFlush;
-        final StringBuilder sb = isSystemApp ? mStrictModeBuffer : new StringBuilder(1024);
+        final StringBuilder sb = new StringBuilder(1024);
         synchronized (sb) {
-            bufferWasEmpty = sb.length() == 0;
             appendDropBoxProcessHeaders(process, processName, sb);
             sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
             sb.append("System-App: ").append(isSystemApp).append("\n");
@@ -14325,75 +14360,18 @@
                 }
             }
             sb.append("\n");
-            if (info.hasStackTrace()) {
-                sb.append(info.getStackTrace());
-                sb.append("\n");
-            }
+            sb.append(info.getStackTrace());
+            sb.append("\n");
             if (info.getViolationDetails() != null) {
                 sb.append(info.getViolationDetails());
                 sb.append("\n");
             }
-
-            // Only buffer up to ~64k.  Various logging bits truncate
-            // things at 128k.
-            needsFlush = (sb.length() > 64 * 1024);
         }
 
-        // Flush immediately if the buffer's grown too large, or this
-        // is a non-system app.  Non-system apps are isolated with a
-        // different tag & policy and not batched.
-        //
-        // Batching is useful during internal testing with
-        // StrictMode settings turned up high.  Without batching,
-        // thousands of separate files could be created on boot.
-        if (!isSystemApp || needsFlush) {
-            new Thread("Error dump: " + dropboxTag) {
-                @Override
-                public void run() {
-                    String report;
-                    synchronized (sb) {
-                        report = sb.toString();
-                        sb.delete(0, sb.length());
-                        sb.trimToSize();
-                    }
-                    if (report.length() != 0) {
-                        dbox.addText(dropboxTag, report);
-                    }
-                }
-            }.start();
-            return;
-        }
-
-        // System app batching:
-        if (!bufferWasEmpty) {
-            // An existing dropbox-writing thread is outstanding, so
-            // we don't need to start it up.  The existing thread will
-            // catch the buffer appends we just did.
-            return;
-        }
-
-        // Worker thread to both batch writes and to avoid blocking the caller on I/O.
-        // (After this point, we shouldn't access AMS internal data structures.)
-        new Thread("Error dump: " + dropboxTag) {
-            @Override
-            public void run() {
-                // 5 second sleep to let stacks arrive and be batched together
-                try {
-                    Thread.sleep(5000);  // 5 seconds
-                } catch (InterruptedException e) {}
-
-                String errorReport;
-                synchronized (mStrictModeBuffer) {
-                    errorReport = mStrictModeBuffer.toString();
-                    if (errorReport.length() == 0) {
-                        return;
-                    }
-                    mStrictModeBuffer.delete(0, mStrictModeBuffer.length());
-                    mStrictModeBuffer.trimToSize();
-                }
-                dbox.addText(dropboxTag, errorReport);
-            }
-        }.start();
+        final String res = sb.toString();
+        IoThread.getHandler().post(() -> {
+            dbox.addText(dropboxTag, res);
+        });
     }
 
     /**
@@ -14660,7 +14638,12 @@
         if (process == null) {
             // If process is null, we are being called from some internal code
             // and may be about to die -- run this synchronously.
-            worker.run();
+            final int oldMask = StrictMode.allowThreadDiskWritesMask();
+            try {
+                worker.run();
+            } finally {
+                StrictMode.setThreadPolicyMask(oldMask);
+            }
         } else {
             worker.start();
         }
@@ -17043,22 +17026,39 @@
         return stringifySize(size * 1024, 1024);
     }
 
-    // Update this version number in case you change the 'compact' format
+    // Update this version number if you change the 'compact' format.
     private static final int MEMINFO_COMPACT_VERSION = 1;
 
+    private static class MemoryUsageDumpOptions {
+        boolean dumpDetails;
+        boolean dumpFullDetails;
+        boolean dumpDalvik;
+        boolean dumpSummaryOnly;
+        boolean dumpUnreachable;
+        boolean oomOnly;
+        boolean isCompact;
+        boolean localOnly;
+        boolean packages;
+        boolean isCheckinRequest;
+        boolean dumpSwapPss;
+        boolean dumpProto;
+    }
+
     final void dumpApplicationMemoryUsage(FileDescriptor fd,
             PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) {
-        boolean dumpDetails = false;
-        boolean dumpFullDetails = false;
-        boolean dumpDalvik = false;
-        boolean dumpSummaryOnly = false;
-        boolean dumpUnreachable = false;
-        boolean oomOnly = false;
-        boolean isCompact = false;
-        boolean localOnly = false;
-        boolean packages = false;
-        boolean isCheckinRequest = false;
-        boolean dumpSwapPss = false;
+        MemoryUsageDumpOptions opts = new MemoryUsageDumpOptions();
+        opts.dumpDetails = false;
+        opts.dumpFullDetails = false;
+        opts.dumpDalvik = false;
+        opts.dumpSummaryOnly = false;
+        opts.dumpUnreachable = false;
+        opts.oomOnly = false;
+        opts.isCompact = false;
+        opts.localOnly = false;
+        opts.packages = false;
+        opts.isCheckinRequest = false;
+        opts.dumpSwapPss = false;
+        opts.dumpProto = false;
 
         int opti = 0;
         while (opti < args.length) {
@@ -17068,29 +17068,31 @@
             }
             opti++;
             if ("-a".equals(opt)) {
-                dumpDetails = true;
-                dumpFullDetails = true;
-                dumpDalvik = true;
-                dumpSwapPss = true;
+                opts.dumpDetails = true;
+                opts.dumpFullDetails = true;
+                opts.dumpDalvik = true;
+                opts.dumpSwapPss = true;
             } else if ("-d".equals(opt)) {
-                dumpDalvik = true;
+                opts.dumpDalvik = true;
             } else if ("-c".equals(opt)) {
-                isCompact = true;
+                opts.isCompact = true;
             } else if ("-s".equals(opt)) {
-                dumpDetails = true;
-                dumpSummaryOnly = true;
+                opts.dumpDetails = true;
+                opts.dumpSummaryOnly = true;
             } else if ("-S".equals(opt)) {
-                dumpSwapPss = true;
+                opts.dumpSwapPss = true;
             } else if ("--unreachable".equals(opt)) {
-                dumpUnreachable = true;
+                opts.dumpUnreachable = true;
             } else if ("--oom".equals(opt)) {
-                oomOnly = true;
+                opts.oomOnly = true;
             } else if ("--local".equals(opt)) {
-                localOnly = true;
+                opts.localOnly = true;
             } else if ("--package".equals(opt)) {
-                packages = true;
+                opts.packages = true;
             } else if ("--checkin".equals(opt)) {
-                isCheckinRequest = true;
+                opts.isCheckinRequest = true;
+            } else if ("--proto".equals(opt)) {
+                opts.dumpProto = true;
 
             } else if ("-h".equals(opt)) {
                 pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
@@ -17104,6 +17106,7 @@
                 pw.println("  --package: interpret process arg as package, dumping all");
                 pw.println("             processes that have loaded that package.");
                 pw.println("  --checkin: dump data for a checkin");
+                pw.println("  --proto: dump data to proto");
                 pw.println("If [process] is specified it can be the name or ");
                 pw.println("pid of a specific process to dump.");
                 return;
@@ -17112,21 +17115,33 @@
             }
         }
 
+        String[] innerArgs = new String[args.length-opti];
+        System.arraycopy(args, opti, innerArgs, 0, args.length-opti);
+
+        ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, opts.packages, args);
+        if (opts.dumpProto) {
+            dumpApplicationMemoryUsage(fd, pw, opts, innerArgs, brief, procs);
+        } else {
+            dumpApplicationMemoryUsage(fd, pw, prefix, opts, innerArgs, brief, procs, categoryPw);
+        }
+    }
+
+    final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix,
+            MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
+            ArrayList<ProcessRecord> procs, PrintWriter categoryPw) {
         long uptime = SystemClock.uptimeMillis();
         long realtime = SystemClock.elapsedRealtime();
         final long[] tmpLong = new long[1];
 
-        ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, packages, args);
         if (procs == null) {
             // No Java processes.  Maybe they want to print a native process.
-            if (args != null && args.length > opti
-                    && args[opti].charAt(0) != '-') {
+            if (innerArgs.length > 0 && innerArgs[0].charAt(0) != '-') {
                 ArrayList<ProcessCpuTracker.Stats> nativeProcs
                         = new ArrayList<ProcessCpuTracker.Stats>();
                 updateCpuStatsNow();
                 int findPid = -1;
                 try {
-                    findPid = Integer.parseInt(args[opti]);
+                    findPid = Integer.parseInt(innerArgs[0]);
                 } catch (NumberFormatException e) {
                 }
                 synchronized (mProcessCpuTracker) {
@@ -17134,51 +17149,48 @@
                     for (int i=0; i<N; i++) {
                         ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
                         if (st.pid == findPid || (st.baseName != null
-                                && st.baseName.equals(args[opti]))) {
+                                && st.baseName.equals(innerArgs[0]))) {
                             nativeProcs.add(st);
                         }
                     }
                 }
                 if (nativeProcs.size() > 0) {
-                    dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest,
-                            isCompact);
+                    dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest,
+                            opts.isCompact);
                     Debug.MemoryInfo mi = null;
                     for (int i = nativeProcs.size() - 1 ; i >= 0 ; i--) {
                         final ProcessCpuTracker.Stats r = nativeProcs.get(i);
                         final int pid = r.pid;
-                        if (!isCheckinRequest && dumpDetails) {
+                        if (!opts.isCheckinRequest && opts.dumpDetails) {
                             pw.println("\n** MEMINFO in pid " + pid + " [" + r.baseName + "] **");
                         }
                         if (mi == null) {
                             mi = new Debug.MemoryInfo();
                         }
-                        if (dumpDetails || (!brief && !oomOnly)) {
+                        if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
                             Debug.getMemoryInfo(pid, mi);
                         } else {
                             mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
                             mi.dalvikPrivateDirty = (int)tmpLong[0];
                         }
-                        ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
-                                dumpDalvik, dumpSummaryOnly, pid, r.baseName, 0, 0, 0, 0, 0, 0);
-                        if (isCheckinRequest) {
+                        ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest, opts.dumpFullDetails,
+                                opts.dumpDalvik, opts.dumpSummaryOnly, pid, r.baseName, 0, 0, 0, 0, 0, 0);
+                        if (opts.isCheckinRequest) {
                             pw.println();
                         }
                     }
                     return;
                 }
             }
-            pw.println("No process found for: " + args[opti]);
+            pw.println("No process found for: " + innerArgs[0]);
             return;
         }
 
-        if (!brief && !oomOnly && (procs.size() == 1 || isCheckinRequest || packages)) {
-            dumpDetails = true;
+        if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) {
+            opts.dumpDetails = true;
         }
 
-        dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest, isCompact);
-
-        String[] innerArgs = new String[args.length-opti];
-        System.arraycopy(args, opti, innerArgs, 0, args.length-opti);
+        dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest, opts.isCompact);
 
         ArrayList<MemItem> procMems = new ArrayList<MemItem>();
         final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
@@ -17186,9 +17198,9 @@
         long nativeSwapPss = 0;
         long dalvikPss = 0;
         long dalvikSwapPss = 0;
-        long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+        long[] dalvikSubitemPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
                 EmptyArray.LONG;
-        long[] dalvikSubitemSwapPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+        long[] dalvikSubitemSwapPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
                 EmptyArray.LONG;
         long otherPss = 0;
         long otherSwapPss = 0;
@@ -17220,24 +17232,24 @@
                 hasActivities = r.activities.size() > 0;
             }
             if (thread != null) {
-                if (!isCheckinRequest && dumpDetails) {
+                if (!opts.isCheckinRequest && opts.dumpDetails) {
                     pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
                 }
                 if (mi == null) {
                     mi = new Debug.MemoryInfo();
                 }
-                if (dumpDetails || (!brief && !oomOnly)) {
+                if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
                     Debug.getMemoryInfo(pid, mi);
                     hasSwapPss = mi.hasSwappedOutPss;
                 } else {
                     mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
                     mi.dalvikPrivateDirty = (int)tmpLong[0];
                 }
-                if (dumpDetails) {
-                    if (localOnly) {
-                        ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
-                                dumpDalvik, dumpSummaryOnly, pid, r.processName, 0, 0, 0, 0, 0, 0);
-                        if (isCheckinRequest) {
+                if (opts.dumpDetails) {
+                    if (opts.localOnly) {
+                        ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest, opts.dumpFullDetails,
+                                opts.dumpDalvik, opts.dumpSummaryOnly, pid, r.processName, 0, 0, 0, 0, 0, 0);
+                        if (opts.isCheckinRequest) {
                             pw.println();
                         }
                     } else {
@@ -17246,19 +17258,19 @@
                             TransferPipe tp = new TransferPipe();
                             try {
                                 thread.dumpMemInfo(tp.getWriteFd(),
-                                        mi, isCheckinRequest, dumpFullDetails,
-                                        dumpDalvik, dumpSummaryOnly, dumpUnreachable, innerArgs);
+                                        mi, opts.isCheckinRequest, opts.dumpFullDetails,
+                                        opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs);
                                 tp.go(fd);
                             } finally {
                                 tp.kill();
                             }
                         } catch (IOException e) {
-                            if (!isCheckinRequest) {
+                            if (!opts.isCheckinRequest) {
                                 pw.println("Got IoException! " + e);
                                 pw.flush();
                             }
                         } catch (RemoteException e) {
-                            if (!isCheckinRequest) {
+                            if (!opts.isCheckinRequest) {
                                 pw.println("Got RemoteException! " + e);
                                 pw.flush();
                             }
@@ -17277,7 +17289,7 @@
                     }
                 }
 
-                if (!isCheckinRequest && mi != null) {
+                if (!opts.isCheckinRequest && mi != null) {
                     totalPss += myTotalPss;
                     totalSwapPss += myTotalSwapPss;
                     MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
@@ -17330,7 +17342,7 @@
 
         long nativeProcTotalPss = 0;
 
-        if (!isCheckinRequest && procs.size() > 1 && !packages) {
+        if (!opts.isCheckinRequest && procs.size() > 1 && !opts.packages) {
             // If we are showing aggregations, also look for native processes to
             // include so that our aggregations are more accurate.
             updateCpuStatsNow();
@@ -17343,7 +17355,7 @@
                         if (mi == null) {
                             mi = new Debug.MemoryInfo();
                         }
-                        if (!brief && !oomOnly) {
+                        if (!brief && !opts.oomOnly) {
                             Debug.getMemoryInfo(st.pid, mi);
                         } else {
                             mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null);
@@ -17430,7 +17442,7 @@
             ArrayList<MemItem> oomMems = new ArrayList<MemItem>();
             for (int j=0; j<oomPss.length; j++) {
                 if (oomPss[j] != 0) {
-                    String label = isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
+                    String label = opts.isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
                             : DUMP_MEM_OOM_LABEL[j];
                     MemItem item = new MemItem(label, label, oomPss[j], oomSwapPss[j],
                             DUMP_MEM_OOM_ADJ[j]);
@@ -17439,26 +17451,26 @@
                 }
             }
 
-            dumpSwapPss = dumpSwapPss && hasSwapPss && totalSwapPss != 0;
-            if (!brief && !oomOnly && !isCompact) {
+            opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && totalSwapPss != 0;
+            if (!brief && !opts.oomOnly && !opts.isCompact) {
                 pw.println();
                 pw.println("Total PSS by process:");
-                dumpMemItems(pw, "  ", "proc", procMems, true, isCompact, dumpSwapPss);
+                dumpMemItems(pw, "  ", "proc", procMems, true, opts.isCompact, opts.dumpSwapPss);
                 pw.println();
             }
-            if (!isCompact) {
+            if (!opts.isCompact) {
                 pw.println("Total PSS by OOM adjustment:");
             }
-            dumpMemItems(pw, "  ", "oom", oomMems, false, isCompact, dumpSwapPss);
-            if (!brief && !oomOnly) {
+            dumpMemItems(pw, "  ", "oom", oomMems, false, opts.isCompact, opts.dumpSwapPss);
+            if (!brief && !opts.oomOnly) {
                 PrintWriter out = categoryPw != null ? categoryPw : pw;
-                if (!isCompact) {
+                if (!opts.isCompact) {
                     out.println();
                     out.println("Total PSS by category:");
                 }
-                dumpMemItems(out, "  ", "cat", catMems, true, isCompact, dumpSwapPss);
+                dumpMemItems(out, "  ", "cat", catMems, true, opts.isCompact, opts.dumpSwapPss);
             }
-            if (!isCompact) {
+            if (!opts.isCompact) {
                 pw.println();
             }
             MemInfoReader memInfo = new MemInfoReader();
@@ -17476,7 +17488,7 @@
                 }
             }
             if (!brief) {
-                if (!isCompact) {
+                if (!opts.isCompact) {
                     pw.print("Total RAM: "); pw.print(stringifyKBSize(memInfo.getTotalSizeKb()));
                     pw.print(" (status ");
                     switch (mLastMemoryLevel) {
@@ -17517,7 +17529,7 @@
             long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
                     - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
                     - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
-            if (!isCompact) {
+            if (!opts.isCompact) {
                 pw.print(" Used RAM: "); pw.print(stringifyKBSize(totalPss - cachedPss
                         + memInfo.getKernelUsedSizeKb())); pw.print(" (");
                 pw.print(stringifyKBSize(totalPss - cachedPss)); pw.print(" used pss + ");
@@ -17528,7 +17540,7 @@
             }
             if (!brief) {
                 if (memInfo.getZramTotalSizeKb() != 0) {
-                    if (!isCompact) {
+                    if (!opts.isCompact) {
                         pw.print("     ZRAM: ");
                         pw.print(stringifyKBSize(memInfo.getZramTotalSizeKb()));
                                 pw.print(" physical used for ");
@@ -17544,7 +17556,7 @@
                     }
                 }
                 final long[] ksm = getKsmInfo();
-                if (!isCompact) {
+                if (!opts.isCompact) {
                     if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0
                             || ksm[KSM_VOLATILE] != 0) {
                         pw.print("      KSM: "); pw.print(stringifyKBSize(ksm[KSM_SHARING]));
@@ -17593,6 +17605,17 @@
         }
     }
 
+    final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw,
+            MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
+            ArrayList<ProcessRecord> procs) {
+        ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        // TODO: implement
+        pw.println("Not yet implemented. Have a cookie instead! :]");
+
+        proto.flush();
+    }
+
     private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss,
             long memtrack, String name) {
         sb.append("  ");
@@ -18816,7 +18839,7 @@
                     Intent intent = allSticky.get(i);
                     BroadcastQueue queue = broadcastQueueForIntent(intent);
                     BroadcastRecord r = new BroadcastRecord(queue, intent, null,
-                            null, -1, -1, false, null, null, AppOpsManager.OP_NONE, null, receivers,
+                            null, -1, -1, false, null, null, OP_NONE, null, receivers,
                             null, 0, null, null, false, true, true, -1);
                     queue.enqueueParallelBroadcastLocked(r);
                     queue.scheduleBroadcastsLocked();
@@ -19812,7 +19835,7 @@
                     : new String[] {requiredPermission};
             int res = broadcastIntentLocked(null, packageName, intent, resolvedType,
                     resultTo, resultCode, resultData, resultExtras,
-                    requiredPermissions, AppOpsManager.OP_NONE, bOptions, serialized,
+                    requiredPermissions, OP_NONE, bOptions, serialized,
                     sticky, -1, uid, userId);
             Binder.restoreCallingIdentity(origId);
             return res;
@@ -20435,7 +20458,7 @@
                 | Intent.FLAG_RECEIVER_FOREGROUND
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
         broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
-                AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                 UserHandle.USER_ALL);
         if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
             intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
@@ -20446,7 +20469,7 @@
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             }
             broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
-                    AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                    OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                     UserHandle.USER_ALL);
         }
 
@@ -20691,9 +20714,10 @@
     // the current [or imminent] receiver on.
     private boolean isReceivingBroadcastLocked(ProcessRecord app,
             ArraySet<BroadcastQueue> receivingQueues) {
-        if (!app.curReceivers.isEmpty()) {
-            for (BroadcastRecord r : app.curReceivers) {
-                receivingQueues.add(r.queue);
+        final int N = app.curReceivers.size();
+        if (N > 0) {
+            for (int i = 0; i < N; i++) {
+                receivingQueues.add(app.curReceivers.valueAt(i).queue);
             }
             return true;
         }
@@ -23857,7 +23881,7 @@
         @Override
         public void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp) {
             synchronized (ActivityManagerService.this) {
-                mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(
+                mStackSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
                         reasons, timestamp);
             }
         }
diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java
index f942265..7eb922c 100644
--- a/com/android/server/am/ActivityManagerShellCommand.java
+++ b/com/android/server/am/ActivityManagerShellCommand.java
@@ -373,7 +373,7 @@
             if (mProfileFile != null || mAgent != null) {
                 ParcelFileDescriptor fd = null;
                 if (mProfileFile != null) {
-                    fd = openOutputFileForSystem(mProfileFile);
+                    fd = openFileForSystem(mProfileFile, "w");
                     if (fd == null) {
                         return 1;
                     }
@@ -668,7 +668,7 @@
 
         File file = new File(filename);
         file.delete();
-        ParcelFileDescriptor fd = openOutputFileForSystem(filename);
+        ParcelFileDescriptor fd = openFileForSystem(filename, "w");
         if (fd == null) {
             return -1;
         }
@@ -756,7 +756,7 @@
 
         if (start) {
             profileFile = getNextArgRequired();
-            fd = openOutputFileForSystem(profileFile);
+            fd = openFileForSystem(profileFile, "w");
             if (fd == null) {
                 return -1;
             }
@@ -820,7 +820,7 @@
 
         File file = new File(heapFile);
         file.delete();
-        ParcelFileDescriptor fd = openOutputFileForSystem(heapFile);
+        ParcelFileDescriptor fd = openFileForSystem(heapFile, "w");
         if (fd == null) {
             return -1;
         }
@@ -2476,7 +2476,10 @@
             pw.println("      -e <NAME> <VALUE>: set argument <NAME> to <VALUE>.  For test runners a");
             pw.println("          common form is [-e <testrunner_flag> <value>[,<value>...]].");
             pw.println("      -p <FILE>: write profiling data to <FILE>");
-            pw.println("      -m: Write output as protobuf (machine readable)");
+            pw.println("      -m: Write output as protobuf to stdout (machine readable)");
+            pw.println("      -f <Optional PATH/TO/FILE>: Write output as protobuf to a file (machine");
+            pw.println("          readable). If path is not specified, default directory and file name will");
+            pw.println("          be used: /sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto");
             pw.println("      -w: wait for instrumentation to finish before returning.  Required for");
             pw.println("          test runners.");
             pw.println("      --user <USER_ID> | current: Specify user instrumentation runs in;");
diff --git a/com/android/server/am/ActivityMetricsLogger.java b/com/android/server/am/ActivityMetricsLogger.java
index 93c0f77..eb022b7 100644
--- a/com/android/server/am/ActivityMetricsLogger.java
+++ b/com/android/server/am/ActivityMetricsLogger.java
@@ -13,6 +13,7 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_BIND_APPLICATION_DELAY_MS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CALLING_PACKAGE_NAME;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CANCELLED;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_IS_EPHEMERAL;
@@ -28,16 +29,22 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_METRICS;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.content.Context;
 import android.metrics.LogMaker;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.SystemClock;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
 
@@ -58,6 +65,8 @@
 
     private static final long INVALID_START_TIME = -1;
 
+    private static final int MSG_CHECK_VISIBILITY = 0;
+
     // Preallocated strings we are sending to tron, so we don't have to allocate a new one every
     // time we log.
     private static final String[] TRON_WINDOW_STATE_VARZ_STRINGS = {
@@ -78,6 +87,23 @@
 
     private final SparseArray<StackTransitionInfo> mStackTransitionInfo = new SparseArray<>();
     private final SparseArray<StackTransitionInfo> mLastStackTransitionInfo = new SparseArray<>();
+    private final H mHandler;
+    private final class H extends Handler {
+
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_CHECK_VISIBILITY:
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    checkVisibility((TaskRecord) args.arg1, (ActivityRecord) args.arg2);
+                    break;
+            }
+        }
+    };
 
     private final class StackTransitionInfo {
         private ActivityRecord launchedActivity;
@@ -91,10 +117,11 @@
         private boolean loggedStartingWindowDrawn;
     }
 
-    ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context) {
+    ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context, Looper looper) {
         mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000;
         mSupervisor = supervisor;
         mContext = context;
+        mHandler = new H(looper);
     }
 
     void logWindowState() {
@@ -145,6 +172,7 @@
      */
     void notifyActivityLaunching() {
         if (!isAnyTransitionActive()) {
+            if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunching");
             mCurrentTransitionStartTime = SystemClock.uptimeMillis();
             mLastTransitionStartTime = mCurrentTransitionStartTime;
         }
@@ -202,6 +230,12 @@
     private void notifyActivityLaunched(int resultCode, ActivityRecord launchedActivity,
             boolean processRunning, boolean processSwitch) {
 
+        if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunched"
+                + " resultCode=" + resultCode
+                + " launchedActivity=" + launchedActivity
+                + " processRunning=" + processRunning
+                + " processSwitch=" + processSwitch);
+
         // If we are already in an existing transition, only update the activity name, but not the
         // other attributes.
         final int stackId = launchedActivity != null && launchedActivity.getStack() != null
@@ -230,6 +264,8 @@
             return;
         }
 
+        if (DEBUG_METRICS) Slog.i(TAG, "notifyActivityLaunched successful");
+
         final StackTransitionInfo newInfo = new StackTransitionInfo();
         newInfo.launchedActivity = launchedActivity;
         newInfo.currentTransitionProcessRunning = processRunning;
@@ -243,6 +279,8 @@
      * Notifies the tracker that all windows of the app have been drawn.
      */
     void notifyWindowsDrawn(int stackId, long timestamp) {
+        if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn stackId=" + stackId);
+
         final StackTransitionInfo info = mStackTransitionInfo.get(stackId);
         if (info == null || info.loggedWindowsDrawn) {
             return;
@@ -276,6 +314,7 @@
         if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
             return;
         }
+        if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting");
         mCurrentTransitionDelayMs = calculateDelay(timestamp);
         mLoggedTransitionStarting = true;
         for (int index = stackIdReasons.size() - 1; index >= 0; index--) {
@@ -295,17 +334,37 @@
      * Notifies the tracker that the visibility of an app is changing.
      *
      * @param activityRecord the app that is changing its visibility
-     * @param visible whether it's going to be visible or not
      */
-    void notifyVisibilityChanged(ActivityRecord activityRecord, boolean visible) {
+    void notifyVisibilityChanged(ActivityRecord activityRecord) {
         final StackTransitionInfo info = mStackTransitionInfo.get(activityRecord.getStackId());
+        if (info == null) {
+            return;
+        }
+        if (info.launchedActivity != activityRecord) {
+            return;
+        }
+        final TaskRecord t = activityRecord.getTask();
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = t;
+        args.arg2 = activityRecord;
+        mHandler.obtainMessage(MSG_CHECK_VISIBILITY, args).sendToTarget();
+    }
 
-        // If we have an active transition that's waiting on a certain activity that will be
-        // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary.
-        if (info != null && !visible && info.launchedActivity == activityRecord) {
-            mStackTransitionInfo.remove(activityRecord.getStackId());
-            if (mStackTransitionInfo.size() == 0) {
-                reset(true /* abort */);
+    private void checkVisibility(TaskRecord t, ActivityRecord r) {
+        synchronized (mSupervisor.mService) {
+
+            final StackTransitionInfo info = mStackTransitionInfo.get(r.getStackId());
+
+            // If we have an active transition that's waiting on a certain activity that will be
+            // invisible now, we'll never get onWindowsDrawn, so abort the transition if necessary.
+            if (info != null && !t.isVisible()) {
+                if (DEBUG_METRICS) Slog.i(TAG, "notifyVisibilityChanged to invisible"
+                        + " activity=" + r);
+                logAppTransitionCancel(info);
+                mStackTransitionInfo.remove(r.getStackId());
+                if (mStackTransitionInfo.size() == 0) {
+                    reset(true /* abort */);
+                }
             }
         }
     }
@@ -341,6 +400,7 @@
     }
 
     private void reset(boolean abort) {
+        if (DEBUG_METRICS) Slog.i(TAG, "reset abort=" + abort);
         if (!abort && isAnyTransitionActive()) {
             logAppTransitionMultiEvents();
         }
@@ -361,7 +421,20 @@
         return (int) (timestamp - mCurrentTransitionStartTime);
     }
 
+    private void logAppTransitionCancel(StackTransitionInfo info) {
+        final int type = getTransitionType(info);
+        if (type == -1) {
+            return;
+        }
+        final LogMaker builder = new LogMaker(APP_TRANSITION_CANCELLED);
+        builder.setPackageName(info.launchedActivity.packageName);
+        builder.setType(type);
+        builder.addTaggedData(FIELD_CLASS_NAME, info.launchedActivity.info.name);
+        mMetricsLogger.write(builder);
+    }
+
     private void logAppTransitionMultiEvents() {
+        if (DEBUG_METRICS) Slog.i(TAG, "logging transition events");
         for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) {
             final StackTransitionInfo info = mStackTransitionInfo.valueAt(index);
             final int type = getTransitionType(info);
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 2f0b649..9a16745 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -77,7 +77,6 @@
 import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
 import static android.os.Build.VERSION_CODES.HONEYCOMB;
 import static android.os.Build.VERSION_CODES.O;
-import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
@@ -202,6 +201,8 @@
     private static final String TAG_STATES = TAG + POSTFIX_STATES;
     private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
     private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
+    // TODO(b/67864419): Remove once recents component is overridden
+    private static final String LEGACY_RECENTS_PACKAGE_NAME = "com.android.systemui.recents";
 
     private static final boolean SHOW_ACTIVITY_START_TIME = true;
 
@@ -886,6 +887,7 @@
 
         Entry ent = AttributeCache.instance().get(packageName,
                 realTheme, com.android.internal.R.styleable.Window, userId);
+
         if (ent != null) {
             fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array);
             hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
@@ -1057,7 +1059,8 @@
                 // We only allow home activities to be resizeable if they explicitly requested it.
                 info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
             }
-        } else if (service.getRecentTasks().isRecentsComponent(realActivity, appInfo.uid)) {
+        } else if (realActivity.getClassName().contains(LEGACY_RECENTS_PACKAGE_NAME) ||
+                service.getRecentTasks().isRecentsComponent(realActivity, appInfo.uid)) {
             activityType = ACTIVITY_TYPE_RECENTS;
         } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
                 && canLaunchAssistActivity(launchedFromPackage)) {
@@ -1526,7 +1529,7 @@
 
     void setVisibility(boolean visible) {
         mWindowContainerController.setVisibility(visible, mDeferHidingClient);
-        mStackSupervisor.mActivityMetricsLogger.notifyVisibilityChanged(this, visible);
+        mStackSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this);
     }
 
     // TODO: Look into merging with #setVisibility()
@@ -1807,7 +1810,7 @@
             }
             stack.mFullyDrawnStartTime = 0;
         }
-        mStackSupervisor.mActivityMetricsLogger.logAppTransitionReportedDrawn(this,
+        mStackSupervisor.getActivityMetricsLogger().logAppTransitionReportedDrawn(this,
                 restoredFromBundle);
         fullyDrawnStartTime = 0;
     }
@@ -1849,7 +1852,7 @@
     @Override
     public void onStartingWindowDrawn(long timestamp) {
         synchronized (service) {
-            mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(
+            mStackSupervisor.getActivityMetricsLogger().notifyStartingWindowDrawn(
                     getStackId(), timestamp);
         }
     }
@@ -1857,7 +1860,7 @@
     @Override
     public void onWindowsDrawn(long timestamp) {
         synchronized (service) {
-            mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId(), timestamp);
+            mStackSupervisor.getActivityMetricsLogger().notifyWindowsDrawn(getStackId(), timestamp);
             if (displayStartTime != 0) {
                 reportLaunchTimeLocked(timestamp);
             }
@@ -2119,11 +2122,6 @@
     }
 
     void setRequestedOrientation(int requestedOrientation) {
-        if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen
-                && appInfo.targetSdkVersion >= O_MR1) {
-            throw new IllegalStateException("Only fullscreen activities can request orientation");
-        }
-
         final int displayId = getDisplayId();
         final Configuration displayConfig =
                 mStackSupervisor.getDisplayOverrideConfiguration(displayId);
@@ -2184,26 +2182,6 @@
         mTmpConfig.unset();
         computeBounds(mTmpBounds);
         if (mTmpBounds.equals(mBounds)) {
-            final ActivityStack stack = getStack();
-            if (!mBounds.isEmpty() || task == null || stack == null || !task.mFullscreen) {
-                // We don't want to influence the override configuration here if our task is in
-                // multi-window mode or there is a bounds specified to calculate the override
-                // config. In both of this cases the app should be compatible with whatever the
-                // current configuration is or will be.
-                return;
-            }
-
-            // Currently limited to the top activity for now to avoid situations where non-top
-            // visible activity and top might have conflicting requests putting the non-top activity
-            // windows in an odd state.
-            final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
-            final Configuration parentConfig = getParent().getConfiguration();
-            if (top != this || isConfigurationCompatible(parentConfig)) {
-                onOverrideConfigurationChanged(mTmpConfig);
-            } else if (isConfigurationCompatible(
-                    mLastReportedConfiguration.getMergedConfiguration())) {
-                onOverrideConfigurationChanged(mLastReportedConfiguration.getMergedConfiguration());
-            }
             return;
         }
 
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index ba41bd4..c086c52 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -16,10 +16,14 @@
 
 package com.android.server.am;
 
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -94,7 +98,6 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityController;
@@ -344,6 +347,7 @@
     private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
     private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
     private final Rect mTmpRect2 = new Rect();
+    private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
 
     /** Run all ActivityStacks through this */
     protected final ActivityStackSupervisor mStackSupervisor;
@@ -451,8 +455,8 @@
         mStackId = stackId;
         mCurrentUser = mService.mUserController.getCurrentUserId();
         mTmpRect2.setEmpty();
-        setWindowingMode(windowingMode);
         setActivityType(activityType);
+        setWindowingMode(windowingMode);
         mWindowContainerController = createStackWindowController(display.mDisplayId, onTop,
                 mTmpRect2);
         postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
@@ -478,6 +482,125 @@
     }
 
     @Override
+    public void setWindowingMode(int windowingMode) {
+        setWindowingMode(windowingMode, false /* animate */);
+    }
+
+    void setWindowingMode(int preferredWindowingMode, boolean animate) {
+        final int currentMode = getWindowingMode();
+        final ActivityDisplay display = getDisplay();
+        final TaskRecord topTask = topTask();
+        final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
+        mTmpOptions.setLaunchWindowingMode(preferredWindowingMode);
+
+        // Need to make sure windowing mode is supported.
+        int windowingMode = display.resolveWindowingMode(
+                null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());;
+        if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            // Resolution to split-screen secondary for the primary split-screen stack means we want
+            // to go fullscreen.
+            windowingMode = WINDOWING_MODE_FULLSCREEN;
+        }
+
+        // Take any required action due to us not supporting the preferred windowing mode.
+        if (windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) {
+            if (display.hasSplitScreenPrimaryStack()
+                    && (preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                    || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
+                // Looks like we can't launch in split screen mode, go ahead an dismiss split-screen
+                // and display a warning toast about it.
+                mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
+                display.getSplitScreenPrimaryStack().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+            }
+        }
+
+        if (currentMode == windowingMode) {
+            // You are already in the window mode silly...
+            return;
+        }
+
+        final WindowManagerService wm = mService.mWindowManager;
+        final ActivityRecord topActivity = getTopActivity();
+
+        if (windowingMode != WINDOWING_MODE_FULLSCREEN && topActivity != null
+                && topActivity.isNonResizableOrForcedResizable() && !topActivity.noDisplay) {
+            // Inform the user that they are starting an app that may not work correctly in
+            // multi-window mode.
+            final String packageName = topActivity.appInfo.packageName;
+            mService.mTaskChangeNotificationController.notifyActivityForcedResizable(
+                    topTask.taskId, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN, packageName);
+        }
+
+        wm.deferSurfaceLayout();
+        try {
+            if (!animate && topActivity != null) {
+                mNoAnimActivities.add(topActivity);
+            }
+            super.setWindowingMode(windowingMode);
+
+            if (mWindowContainerController == null) {
+                // Nothing else to do if we don't have a window container yet. E.g. call from ctor.
+                return;
+            }
+
+            if (windowingMode == WINDOWING_MODE_PINNED || currentMode == WINDOWING_MODE_PINNED) {
+                // TODO: Need to remove use of PinnedActivityStack for this to be supported.
+                // NOTE: Need to ASS.scheduleUpdatePictureInPictureModeIfNeeded() in
+                // setWindowModeUnchecked() when this support is added. See TaskRecord.reparent()
+                throw new IllegalArgumentException(
+                        "Changing pinned windowing mode not currently supported");
+            }
+
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && splitScreenStack != null) {
+                // We already have a split-screen stack in this display, so just move the tasks over.
+                // TODO: Figure-out how to do all the stuff in
+                // AMS.setTaskWindowingModeSplitScreenPrimary
+                throw new IllegalArgumentException("Setting primary split-screen windowing mode"
+                        + " while there is already one isn't currently supported");
+                //return;
+            }
+
+            mTmpRect2.setEmpty();
+            if (windowingMode != WINDOWING_MODE_FULLSCREEN) {
+                mWindowContainerController.getRawBounds(mTmpRect2);
+                if (windowingMode == WINDOWING_MODE_FREEFORM) {
+                    if (topTask != null) {
+                        // TODO: Can we consolidate this and other sites that call this methods?
+                        Rect bounds = topTask().getLaunchBounds();
+                        if (bounds != null) {
+                            mTmpRect2.set(bounds);
+                        }
+                    }
+                }
+            }
+
+            if (!Objects.equals(mBounds, mTmpRect2)) {
+                resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
+            }
+        } finally {
+            if (mDisplayId == DEFAULT_DISPLAY
+                    && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                // Make sure recents stack exist when creating a dock stack as it normally needs to
+                // be on the other side of the docked stack and we make visibility decisions based
+                // on that.
+                // TODO: This is only here to help out with the case where recents stack doesn't
+                // exist yet. For that case the initial size of the split-screen stack will be the
+                // the one where the home stack is visible since recents isn't visible yet, but the
+                // divider will be off. I think we should just make the initial bounds that of home
+                // so that the divider matches and remove this logic.
+                display.getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                        ACTIVITY_TYPE_RECENTS, true /* onTop */);
+                // If task moved to docked stack - show recents if needed.
+                mService.mWindowManager.showRecentApps(false /* fromHome */);
+            }
+            wm.continueSurfaceLayout();
+        }
+
+        mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+        mStackSupervisor.resumeFocusedStackTopActivityLocked();
+    }
+
+    @Override
     public boolean isCompatible(int windowingMode, int activityType) {
         // TODO: Should we just move this to ConfigurationContainer?
         if (activityType == ACTIVITY_TYPE_UNDEFINED) {
@@ -537,12 +660,6 @@
      * either destroyed completely or re-parented.
      */
     private void removeFromDisplay() {
-        if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            // If we removed a docked stack we want to resize it so it resizes all other stacks
-            // in the system to fullscreen.
-            mStackSupervisor.resizeDockedStackLocked(
-                    null, null, null, null, null, PRESERVE_WINDOWS);
-        }
         final ActivityDisplay display = getDisplay();
         if (display != null) {
             display.removeChild(this);
@@ -729,14 +846,11 @@
         return null;
     }
 
-    final ActivityRecord topActivity() {
+    ActivityRecord getTopActivity() {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
-                if (!r.finishing) {
-                    return r;
-                }
+            final ActivityRecord r = mTaskHistory.get(taskNdx).getTopActivity();
+            if (r != null) {
+                return r;
             }
         }
         return null;
@@ -750,7 +864,7 @@
         return null;
     }
 
-    final TaskRecord bottomTask() {
+    private TaskRecord bottomTask() {
         if (mTaskHistory.isEmpty()) {
             return null;
         }
@@ -870,6 +984,29 @@
         }
     }
 
+    /**
+     * @param reason The reason for moving the stack to the back.
+     * @param task If non-null, the task will be moved to the bottom of the stack.
+     **/
+    void moveToBack(String reason, TaskRecord task) {
+        if (!isAttached()) {
+            return;
+        }
+
+        getDisplay().positionChildAtBottom(this);
+        mStackSupervisor.setFocusStackUnchecked(reason, getDisplay().getTopStack());
+        if (task != null) {
+            insertTaskAtBottom(task);
+            return;
+        } else {
+            task = bottomTask();
+            if (task != null) {
+                mWindowContainerController.positionChildAtBottom(
+                        task.getWindowContainerController(), true /* includingParents */);
+            }
+        }
+    }
+
     boolean isFocusable() {
         if (getWindowConfiguration().canReceiveKeys()) {
             return true;
@@ -1583,8 +1720,6 @@
             }
 
             final int otherWindowingMode = other.getWindowingMode();
-            // TODO: Can be removed once we are no longer using returnToType for back functionality
-            final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null;
 
             if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
                 if (other.isStackTranslucent(starting)) {
@@ -1645,7 +1780,7 @@
             boolean preserveWindows) {
         mTopActivityOccludesKeyguard = false;
         mTopDismissingKeyguardActivity = null;
-        mStackSupervisor.mKeyguardController.beginActivityVisibilityUpdate();
+        mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
         try {
             ActivityRecord top = topRunningActivityLocked();
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "ensureActivitiesVisible behind " + top
@@ -1753,7 +1888,7 @@
                 notifyActivityDrawnLocked(null);
             }
         } finally {
-            mStackSupervisor.mKeyguardController.endActivityVisibilityUpdate();
+            mStackSupervisor.getKeyguardController().endActivityVisibilityUpdate();
         }
     }
 
@@ -1778,6 +1913,22 @@
         return inPinnedWindowingMode();
     }
 
+    @Override
+    public boolean supportsSplitScreenWindowingMode() {
+        final TaskRecord topTask = topTask();
+        return super.supportsSplitScreenWindowingMode()
+                && (topTask == null || topTask.supportsSplitScreenWindowingMode());
+    }
+
+    /** @return True if the resizing of the primary-split-screen stack affects this stack size. */
+    boolean affectedBySplitScreenResize() {
+        if (!supportsSplitScreenWindowingMode()) {
+            return false;
+        }
+        final int windowingMode = getWindowingMode();
+        return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
+    }
+
     /**
      * @return the top most visible activity that wants to dismiss Keyguard
      */
@@ -1795,9 +1946,9 @@
     boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible,
             boolean isTop) {
         final boolean isInPinnedStack = r.inPinnedWindowingMode();
-        final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing(
+        final boolean keyguardShowing = mStackSupervisor.getKeyguardController().isKeyguardShowing(
                 mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
-        final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked();
+        final boolean keyguardLocked = mStackSupervisor.getKeyguardController().isKeyguardLocked();
         final boolean showWhenLocked = r.canShowWhenLocked() && !isInPinnedStack;
         final boolean dismissKeyguard = r.hasDismissKeyguardWindows();
         if (shouldBeVisible) {
@@ -1812,7 +1963,7 @@
             }
 
             final boolean canShowWithKeyguard = canShowWithInsecureKeyguard()
-                    && mStackSupervisor.mKeyguardController.canDismissKeyguard();
+                    && mStackSupervisor.getKeyguardController().canDismissKeyguard();
             if (canShowWithKeyguard) {
                 return true;
             }
@@ -1821,10 +1972,10 @@
 
             // If keyguard is showing, nothing is visible, except if we are able to dismiss Keyguard
             // right away.
-            return shouldBeVisible && mStackSupervisor.mKeyguardController
+            return shouldBeVisible && mStackSupervisor.getKeyguardController()
                     .canShowActivityWhileKeyguardShowing(r, dismissKeyguard);
         } else if (keyguardLocked) {
-            return shouldBeVisible && mStackSupervisor.mKeyguardController.canShowWhileOccluded(
+            return shouldBeVisible && mStackSupervisor.getKeyguardController().canShowWhileOccluded(
                     dismissKeyguard, showWhenLocked);
         } else {
             return shouldBeVisible;
@@ -2388,7 +2539,7 @@
                     // if needed to get the correct rotation behavior.
                     // TODO: Remove this once visibilities are set correctly immediately when
                     // starting an activity.
-                    if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
+                    if (mStackSupervisor.getKeyguardController().isKeyguardLocked()) {
                         mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
                                 0 /* configChanges */, false /* preserveWindows */);
                     }
@@ -2581,6 +2732,9 @@
         if (position >= mTaskHistory.size()) {
             insertTaskAtTop(task, null);
             return;
+        } else if (position <= 0) {
+            insertTaskAtBottom(task);
+            return;
         }
         position = getAdjustedPositionForTask(task, position, null /* starting */);
         mTaskHistory.remove(task);
@@ -2601,6 +2755,16 @@
                 true /* includingParents */);
     }
 
+    private void insertTaskAtBottom(TaskRecord task) {
+        // Unlike insertTaskAtPosition, this will also position parents of the windowcontroller.
+        mTaskHistory.remove(task);
+        final int position = getAdjustedPositionForTask(task, 0, null);
+        mTaskHistory.add(position, task);
+        updateTaskMovement(task, true);
+        mWindowContainerController.positionChildAtBottom(task.getWindowContainerController(),
+                true /* includingParents */);
+    }
+
     final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
             boolean newTask, boolean keepCurTransition, ActivityOptions options) {
         TaskRecord rTask = r.getTask();
@@ -3474,7 +3638,7 @@
                 }
 
                 if (endTask) {
-                    mService.mLockTaskController.removeLockedTask(task);
+                    mService.mLockTaskController.clearLockedTask(task);
                 }
             } else if (r.state != ActivityState.PAUSING) {
                 // If the activity is PAUSING, we will complete the finish once
@@ -4253,7 +4417,7 @@
         if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
 
         final ActivityStack topStack = getDisplay().getTopStack();
-        final ActivityRecord topActivity = topStack != null ? topStack.topActivity() : null;
+        final ActivityRecord topActivity = topStack != null ? topStack.getTopActivity() : null;
         final int numTasks = mTaskHistory.size();
         final int index = mTaskHistory.indexOf(tr);
         if (numTasks == 0 || index < 0)  {
@@ -4334,8 +4498,9 @@
         }
         Slog.i(TAG, "moveTaskToBack: " + tr);
 
-        // If the task is locked, then show the lock task toast
-        if (mService.mLockTaskController.checkLockedTask(tr)) {
+        // In LockTask mode, moving a locked task to the back of the stack may expose unlocked
+        // ones. Therefore we need to check if this operation is allowed.
+        if (!mService.mLockTaskController.canMoveTaskToBack(tr)) {
             return false;
         }
 
@@ -4369,8 +4534,7 @@
         updateTaskMovement(tr, false);
 
         mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
-        mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(),
-                true /* includingParents */);
+        moveToBack("moveTaskToBackLocked", tr);
 
         if (inPinnedWindowingMode()) {
             mStackSupervisor.removeStack(this);
@@ -4456,6 +4620,7 @@
             final TaskRecord task = mTaskHistory.get(i);
             if (task.isResizeable()) {
                 if (inFreeformWindowingMode()) {
+                    // TODO: Can be removed now since each freeform task is in its own stack.
                     // For freeform stack we don't adjust the size of the tasks to match that
                     // of the stack, but we do try to make sure the tasks are still contained
                     // with the bounds of the stack.
@@ -4843,7 +5008,7 @@
                 voiceInteractor);
         // add the task to stack first, mTaskPositioner might need the stack association
         addTask(task, toTop, "createTaskRecord");
-        final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController
+        final boolean isLockscreenShown = mService.mStackSupervisor.getKeyguardController()
                 .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
         if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
                 && mBounds != null && task.isResizeable() && !isLockscreenShown) {
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index 6ec158e..745e9fb 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.START_ANY_ACTIVITY;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
@@ -83,6 +84,7 @@
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
 import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
@@ -293,7 +295,7 @@
     WindowManagerService mWindowManager;
     DisplayManager mDisplayManager;
 
-    private final LaunchingBoundsController mLaunchingBoundsController;
+    private LaunchingBoundsController mLaunchingBoundsController;
 
     /** Counter for next free stack ID to use for dynamic activity stacks. */
     private int mNextFreeStackId = 0;
@@ -393,7 +395,6 @@
     private final SparseArray<IntArray> mDisplayAccessUIDs = new SparseArray<>();
 
     private DisplayManagerInternal mDisplayManagerInternal;
-    private InputManagerInternal mInputManagerInternal;
 
     /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
     boolean inResumeTopActivity;
@@ -412,7 +413,7 @@
     // Whether tasks have moved and we need to rank the tasks before next OOM scoring
     private boolean mTaskLayersChanged = true;
 
-    final ActivityMetricsLogger mActivityMetricsLogger;
+    private ActivityMetricsLogger mActivityMetricsLogger;
 
     private final ArrayList<ActivityRecord> mTmpActivityList = new ArrayList<>();
 
@@ -532,11 +533,13 @@
      */
     boolean mIsDockMinimized;
 
-    final KeyguardController mKeyguardController;
+    private KeyguardController mKeyguardController;
 
     private PowerManager mPowerManager;
     private int mDeferResumeCount;
 
+    private boolean mInitialized;
+
     /**
      * Description of a request to start a new activity, which has been held
      * due to app switches being disabled.
@@ -572,14 +575,32 @@
     public ActivityStackSupervisor(ActivityManagerService service, Looper looper) {
         mService = service;
         mHandler = new ActivityStackSupervisorHandler(looper);
+    }
+
+    public void initialize() {
+        if (mInitialized) {
+            return;
+        }
+
+        mInitialized = true;
         mRunningTasks = createRunningTasks();
-        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
-        mKeyguardController = new KeyguardController(service, this);
+        mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext,
+                mHandler.getLooper());
+        mKeyguardController = new KeyguardController(mService, this);
 
         mLaunchingBoundsController = new LaunchingBoundsController();
         mLaunchingBoundsController.registerDefaultPositioners(this);
     }
 
+
+    public ActivityMetricsLogger getActivityMetricsLogger() {
+        return mActivityMetricsLogger;
+    }
+
+    public KeyguardController getKeyguardController() {
+        return mKeyguardController;
+    }
+
     void setRecentTasks(RecentTasks recentTasks) {
         mRecentTasks = recentTasks;
         mRecentTasks.registerCallback(this);
@@ -622,8 +643,6 @@
 
             mHomeStack = mFocusedStack = mLastFocusedStack = getDefaultDisplay().getOrCreateStack(
                     WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
-
-            mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         }
     }
 
@@ -1306,8 +1325,11 @@
             mService.updateLruProcessLocked(app, true, null);
             mService.updateOomAdjLocked();
 
-            if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE ||
-                    task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV) {
+            if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+                    || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV
+                    || (task.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED
+                            && mService.mLockTaskController.getLockTaskModeState()
+                            == LOCK_TASK_MODE_LOCKED)) {
                 mService.mLockTaskController.startLockTaskMode(task, false, 0 /* blank UID */);
             }
 
@@ -2436,7 +2458,7 @@
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stack.mStackId);
         mWindowManager.deferSurfaceLayout();
         try {
-            if (stack.supportsSplitScreenWindowingMode()) {
+            if (stack.affectedBySplitScreenResize()) {
                 if (bounds == null && stack.inSplitScreenWindowingMode()) {
                     // null bounds = fullscreen windowing mode...at least for now.
                     stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -2536,8 +2558,6 @@
                                 null, mTmpOptions, task, task.getActivityType(), onTop);
 
                     if (onTop) {
-                        final int returnToType =
-                                toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
                         final boolean isTopTask = i == (size - 1);
                         // Defer resume until all the tasks have been moved to the fullscreen stack
                         task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
@@ -2626,7 +2646,7 @@
                     if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                         continue;
                     }
-                    if (!current.supportsSplitScreenWindowingMode()) {
+                    if (!current.affectedBySplitScreenResize()) {
                         continue;
                     }
                     // Need to set windowing mode here before we try to get the dock bounds.
@@ -2769,6 +2789,7 @@
         if (tr != null) {
             tr.removeTaskActivitiesLocked(pauseImmediately);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
+            mService.mLockTaskController.clearLockedTask(tr);
             if (tr.isPersistable) {
                 mService.notifyTaskPersisterLocked(null, true);
             }
@@ -2941,6 +2962,7 @@
      * Returns the reparent target stack, creating the stack if necessary.  This call also enforces
      * the various checks on tasks that are going to be reparented from one stack to another.
      */
+    // TODO: Look into changing users to this method to ActivityDisplay.resolveWindowingMode()
     ActivityStack getReparentTargetStack(TaskRecord task, ActivityStack stack, boolean toTop) {
         final ActivityStack prevStack = task.getStack();
         final int stackId = stack.mStackId;
@@ -2967,8 +2989,7 @@
                     + " reparent task=" + task + " to stackId=" + stackId);
         }
 
-        // Ensure that we aren't trying to move into a freeform stack without freeform
-        // support
+        // Ensure that we aren't trying to move into a freeform stack without freeform support
         if (stack.getWindowingMode() == WINDOWING_MODE_FREEFORM
                 && !mService.mSupportsFreeformWindowManagement) {
             throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
@@ -3372,7 +3393,7 @@
 
         // When launching tasks behind, update the last active time of the top task after the new
         // task has been shown briefly
-        final ActivityRecord top = stack.topActivity();
+        final ActivityRecord top = stack.getTopActivity();
         if (top != null) {
             top.getTask().touchActiveTime();
         }
@@ -4171,8 +4192,8 @@
         }
 
         // Handle incorrect launch/move to secondary display if needed.
-        final boolean launchOnSecondaryDisplayFailed;
         if (isSecondaryDisplayPreferred) {
+            final boolean launchOnSecondaryDisplayFailed;
             final int actualDisplayId = task.getStack().mDisplayId;
             if (!task.canBeLaunchedOnDisplay(actualDisplayId)) {
                 // The task landed on an inappropriate display somehow, move it to the default
@@ -4187,34 +4208,34 @@
                         || (preferredDisplayId != INVALID_DISPLAY
                             && preferredDisplayId != actualDisplayId);
             }
-        } else {
-            // The task wasn't requested to be on a secondary display.
-            launchOnSecondaryDisplayFailed = false;
-        }
-
-        final ActivityRecord topActivity = task.getTopActivity();
-        if (launchOnSecondaryDisplayFailed
-                || !task.supportsSplitScreenWindowingMode() || forceNonResizable) {
             if (launchOnSecondaryDisplayFailed) {
                 // Display a warning toast that we tried to put a non-resizeable task on a secondary
                 // display with config different from global config.
                 mService.mTaskChangeNotificationController
                         .notifyActivityLaunchOnSecondaryDisplayFailed();
-            } else {
-                // Display a warning toast that we tried to put a non-dockable task in the docked
-                // stack.
-                mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
+                return;
             }
+        }
+
+        if (!task.supportsSplitScreenWindowingMode() || forceNonResizable) {
+            // Display a warning toast that we tried to put an app that doesn't support split-screen
+            // in split-screen.
+            mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
 
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
 
-            final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenPrimaryStack();
+            final ActivityStack dockedStack =
+                    task.getStack().getDisplay().getSplitScreenPrimaryStack();
             if (dockedStack != null) {
                 moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack);
             }
-        } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
-                && !topActivity.noDisplay) {
+            return;
+        }
+
+        final ActivityRecord topActivity = task.getTopActivity();
+        if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
+            && !topActivity.noDisplay) {
             final String packageName = topActivity.appInfo.packageName;
             final int reason = isSecondaryDisplayPreferred
                     ? FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY
@@ -4465,7 +4486,7 @@
         try {
             if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 mWindowManager.setDockedStackCreateState(
-                        activityOptions.getDockCreateMode(), null /* initialBounds */);
+                        activityOptions.getSplitScreenCreateMode(), null /* initialBounds */);
 
                 // Defer updating the stack in which recents is until the app transition is done, to
                 // not run into issues where we still need to draw the task in recents but the
@@ -4483,9 +4504,12 @@
                         "startActivityFromRecents: Task " + taskId + " not found.");
             }
 
-            // We always want to return to the home activity instead of the recents activity from
-            // whatever is started from the recents activity, so move the home stack forward.
-            moveHomeStackToFront("startActivityFromRecents");
+            if (windowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                // We always want to return to the home activity instead of the recents activity
+                // from whatever is started from the recents activity, so move the home stack
+                // forward.
+                moveHomeStackToFront("startActivityFromRecents");
+            }
 
             // If the user must confirm credentials (e.g. when first launching a work app and the
             // Work Challenge is present) let startActivityInPackage handle the intercepting.
@@ -4496,9 +4520,13 @@
                 mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */,
                         targetActivity);
                 mActivityMetricsLogger.notifyActivityLaunching();
-                mService.moveTaskToFrontLocked(task.taskId, 0, bOptions, true /* fromRecents */);
-                mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,
-                        targetActivity);
+                try {
+                    mService.moveTaskToFrontLocked(task.taskId, 0, bOptions,
+                            true /* fromRecents */);
+                } finally {
+                    mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT,
+                            targetActivity);
+                }
 
                 // If we are launching the task in the docked stack, put it into resizing mode so
                 // the window renders full-screen with the background filling the void. Also only
@@ -4541,7 +4569,7 @@
                 final ActivityStack stack = display.getChildAt(j);
                 // Get top activity from a visible stack and add it to the list.
                 if (stack.shouldBeVisible(null /* starting */)) {
-                    final ActivityRecord top = stack.topActivity();
+                    final ActivityRecord top = stack.getTopActivity();
                     if (top != null) {
                         if (stack == mFocusedStack) {
                             topActivityTokens.add(0, top.appToken);
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index 1c80282..9b8cbc1 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -90,6 +90,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.AuxiliaryResolveInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
@@ -133,6 +134,7 @@
     private static final int INVALID_LAUNCH_MODE = -1;
 
     private final ActivityManagerService mService;
+    private final IPackageManager mPackageManager;
     private final ActivityStackSupervisor mSupervisor;
     private final ActivityStartInterceptor mInterceptor;
 
@@ -232,8 +234,9 @@
         mIntentDelivered = false;
     }
 
-    ActivityStarter(ActivityManagerService service) {
+    ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
         mService = service;
+        mPackageManager = packageManager;
         mSupervisor = mService.mStackSupervisor;
         mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
     }
@@ -264,8 +267,12 @@
             outActivity[0] = mLastStartActivityRecord[0];
         }
 
+        return getExternalResult(mLastStartActivityResult);
+    }
+
+    public static int getExternalResult(int result) {
         // Aborted results are treated as successes externally, but we must track them internally.
-        return mLastStartActivityResult != START_ABORTED ? mLastStartActivityResult : START_SUCCESS;
+        return result != START_ABORTED ? result : START_SUCCESS;
     }
 
     /** DO NOT call this method directly. Use {@link #startActivityLocked} instead. */
@@ -295,7 +302,8 @@
             }
         }
 
-        final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
+        final int userId = aInfo != null && aInfo.applicationInfo != null
+                ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
 
         if (err == ActivityManager.START_SUCCESS) {
             Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
@@ -371,7 +379,7 @@
                     && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
                 try {
                     intent.addCategory(Intent.CATEGORY_VOICE);
-                    if (!AppGlobals.getPackageManager().activitySupportsIntent(
+                    if (!mPackageManager.activitySupportsIntent(
                             intent.getComponent(), intent, resolvedType)) {
                         Slog.w(TAG,
                                 "Activity being started in current voice task does not support voice: "
@@ -389,7 +397,7 @@
             // If the caller is starting a new voice session, just make sure the target
             // is actually allowing it to run this way.
             try {
-                if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+                if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
                         intent, resolvedType)) {
                     Slog.w(TAG,
                             "Activity being started in new voice task does not support: "
@@ -665,7 +673,7 @@
         if (intent != null && intent.hasFileDescriptors()) {
             throw new IllegalArgumentException("File descriptors passed in Intent");
         }
-        mSupervisor.mActivityMetricsLogger.notifyActivityLaunching();
+        mSupervisor.getActivityMetricsLogger().notifyActivityLaunching();
         boolean componentSpecified = intent.getComponent() != null;
 
         // Save a copy in case ephemeral needs it
@@ -859,7 +867,7 @@
                 }
             }
 
-            mSupervisor.mActivityMetricsLogger.notifyActivityLaunched(res, outRecord[0]);
+            mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outRecord[0]);
             return res;
         }
     }
@@ -1119,7 +1127,7 @@
         // If the activity being launched is the same as the one currently at the top, then
         // we need to check if it should only be launched once.
         final ActivityStack topStack = mSupervisor.mFocusedStack;
-        final ActivityRecord topFocused = topStack.topActivity();
+        final ActivityRecord topFocused = topStack.getTopActivity();
         final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
         final boolean dontStart = top != null && mStartActivity.resultTo == null
                 && top.realActivity.equals(mStartActivity.realActivity)
@@ -1565,8 +1573,8 @@
                 && (topTask != intentActivity.getTask() || topTask != focusStack.topTask())
                 && !mAvoidMoveToFront) {
             mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
-            if (mSourceRecord == null || (mSourceStack.topActivity() != null &&
-                    mSourceStack.topActivity().getTask() == mSourceRecord.getTask())) {
+            if (mSourceRecord == null || (mSourceStack.getTopActivity() != null &&
+                    mSourceStack.getTopActivity().getTask() == mSourceRecord.getTask())) {
                 // We really do want to push this one into the user's face, right now.
                 if (mLaunchTaskBehind && mSourceRecord != null) {
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
@@ -1956,7 +1964,7 @@
         if (mDoResume) {
             mTargetStack.moveToFront("addingToTopTask");
         }
-        final ActivityRecord prev = mTargetStack.topActivity();
+        final ActivityRecord prev = mTargetStack.getTopActivity();
         final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord(
                 mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
                 mIntent, null, null, true);
diff --git a/com/android/server/am/AssistDataReceiverProxy.java b/com/android/server/am/AssistDataReceiverProxy.java
new file mode 100644
index 0000000..9991ce1
--- /dev/null
+++ b/com/android/server/am/AssistDataReceiverProxy.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.IAssistDataReceiver;
+import android.graphics.Bitmap;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+
+/**
+ * Proxies assist data to the given receiver, skipping all callbacks if the receiver dies.
+ */
+class AssistDataReceiverProxy implements AssistDataRequesterCallbacks,
+        Binder.DeathRecipient {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AssistDataReceiverProxy" : TAG_AM;
+
+    private String mCallerPackage;
+    private IAssistDataReceiver mReceiver;
+
+    public AssistDataReceiverProxy(IAssistDataReceiver receiver, String callerPackage) {
+        mReceiver = receiver;
+        mCallerPackage = callerPackage;
+        linkToDeath();
+    }
+
+    @Override
+    public boolean canHandleReceivedAssistDataLocked() {
+        // We are forwarding, so we can always receive this data
+        return true;
+    }
+
+    @Override
+    public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+        if (mReceiver != null) {
+            try {
+                mReceiver.onHandleAssistData(data);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to proxy assist data to receiver in package="
+                        + mCallerPackage, e);
+            }
+        }
+    }
+
+    @Override
+    public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+        if (mReceiver != null) {
+            try {
+                mReceiver.onHandleAssistScreenshot(screenshot);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to proxy assist screenshot to receiver in package="
+                        + mCallerPackage, e);
+            }
+        }
+    }
+
+    @Override
+    public void onAssistRequestCompleted() {
+        unlinkToDeath();
+    }
+
+    @Override
+    public void binderDied() {
+        unlinkToDeath();
+    }
+
+    private void linkToDeath() {
+        try {
+            mReceiver.asBinder().linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Could not link to client death", e);
+        }
+    }
+
+    private void unlinkToDeath() {
+        if (mReceiver != null) {
+            mReceiver.asBinder().unlinkToDeath(this, 0);
+        }
+        mReceiver = null;
+    }
+}
\ No newline at end of file
diff --git a/com/android/server/am/AssistDataRequester.java b/com/android/server/am/AssistDataRequester.java
new file mode 100644
index 0000000..9f7621f
--- /dev/null
+++ b/com/android/server/am/AssistDataRequester.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.ASSIST_CONTEXT_FULL;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_NONE;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.IAssistDataReceiver;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to asynchronously fetch the assist data and screenshot from the current running
+ * activities. It manages received data and calls back to the owner when the owner is ready to
+ * receive the data itself.
+ */
+public class AssistDataRequester extends IAssistDataReceiver.Stub {
+
+    public static final String KEY_RECEIVER_EXTRA_COUNT = "count";
+    public static final String KEY_RECEIVER_EXTRA_INDEX = "index";
+
+    private IActivityManager mService;
+    private IWindowManager mWindowManager;
+    private Context mContext;
+    private AppOpsManager mAppOpsManager;
+
+    private AssistDataRequesterCallbacks mCallbacks;
+    private Object mCallbacksLock;
+
+    private int mRequestStructureAppOps;
+    private int mRequestScreenshotAppOps;
+    private boolean mCanceled;
+    private int mPendingDataCount;
+    private int mPendingScreenshotCount;
+    private final ArrayList<Bundle> mAssistData = new ArrayList<>();
+    private final ArrayList<Bitmap> mAssistScreenshot = new ArrayList<>();
+
+
+    /**
+     * Interface to handle the events from the fetcher.
+     */
+    public interface AssistDataRequesterCallbacks {
+        /**
+         * @return whether the currently received assist data can be handled by the callbacks.
+         */
+        @GuardedBy("mCallbacksLock")
+        boolean canHandleReceivedAssistDataLocked();
+
+        /**
+         * Called when we receive asynchronous assist data. This call is only made if the
+         * {@param fetchData} argument to requestAssistData() is true, and if the current activity
+         * allows assist data to be fetched.  In addition, the callback will be made with the
+         * {@param mCallbacksLock} held, and only if {@link #canHandleReceivedAssistDataLocked()}
+         * is true.
+         */
+        @GuardedBy("mCallbacksLock")
+        void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount);
+
+        /**
+         * Called when we receive asynchronous assist screenshot. This call is only made if
+         * {@param fetchScreenshot} argument to requestAssistData() is true, and if the current
+         * activity allows assist data to be fetched.  In addition, the callback will be made with
+         * the {@param mCallbacksLock} held, and only if
+         * {@link #canHandleReceivedAssistDataLocked()} is true.
+         */
+        @GuardedBy("mCallbacksLock")
+        void onAssistScreenshotReceivedLocked(Bitmap screenshot);
+
+        /**
+         * Called when there is no more pending assist data or screenshots for the last request.
+         * If the request was canceled, then this callback will not be made. In addition, the
+         * callback will be made with the {@param mCallbacksLock} held, and only if
+         * {@link #canHandleReceivedAssistDataLocked()} is true.
+         */
+        @GuardedBy("mCallbacksLock")
+        default void onAssistRequestCompleted() {
+            // Do nothing
+        }
+    }
+
+    /**
+     * @param callbacks The callbacks to handle the asynchronous reply with the assist data.
+     * @param callbacksLock The lock for the requester to hold when calling any of the
+     *                     {@param callbacks}. The owner should also take care in locking
+     *                     appropriately when calling into this requester.
+     * @param requestStructureAppOps The app ops to check before requesting the assist structure
+     * @param requestScreenshotAppOps The app ops to check before requesting the assist screenshot.
+     *                                This can be {@link AppOpsManager#OP_NONE} to indicate that
+     *                                screenshots should never be fetched.
+     */
+    public AssistDataRequester(Context context, IActivityManager service,
+            IWindowManager windowManager, AppOpsManager appOpsManager,
+            AssistDataRequesterCallbacks callbacks, Object callbacksLock,
+            int requestStructureAppOps, int requestScreenshotAppOps) {
+        mCallbacks = callbacks;
+        mCallbacksLock = callbacksLock;
+        mWindowManager = windowManager;
+        mService = service;
+        mContext = context;
+        mAppOpsManager = appOpsManager;
+        mRequestStructureAppOps = requestStructureAppOps;
+        mRequestScreenshotAppOps = requestScreenshotAppOps;
+    }
+
+    /**
+     * Request that assist data be loaded asynchronously. The resulting data will be provided
+     * through the {@link AssistDataRequesterCallbacks}.
+     *
+     * @param activityTokens the list of visible activities
+     * @param fetchData whether or not to fetch the assist data, only applies if the caller is
+     *     allowed to fetch the assist data, and the current activity allows assist data to be
+     *     fetched from it
+     * @param fetchScreenshot whether or not to fetch the screenshot, only applies if fetchData is
+     *     true, the caller is allowed to fetch the assist data, and the current activity allows
+     *     assist data to be fetched from it
+     * @param allowFetchData to be joined with other checks, determines whether or not the requester
+     *     is allowed to fetch the assist data
+     * @param allowFetchScreenshot to be joined with other checks, determines whether or not the
+     *     requester is allowed to fetch the assist screenshot
+     */
+    public void requestAssistData(List<IBinder> activityTokens, final boolean fetchData,
+            final boolean fetchScreenshot, boolean allowFetchData, boolean allowFetchScreenshot,
+            int callingUid, String callingPackage) {
+        // TODO(b/34090158): Known issue, if the assist data is not allowed on the current activity,
+        //                   then no assist data is requested for any of the other activities
+
+        // Early exit if there are no activity to fetch for
+        if (activityTokens.isEmpty()) {
+            // No activities, just dispatch request-complete
+            tryDispatchRequestComplete();
+            return;
+        }
+
+        // Ensure that the current activity supports assist data
+        boolean isAssistDataAllowed = false;
+        try {
+            isAssistDataAllowed = mService.isAssistDataAllowedOnCurrentActivity();
+        } catch (RemoteException e) {
+            // Should never happen
+        }
+        allowFetchData &= isAssistDataAllowed;
+        allowFetchScreenshot &= fetchData && isAssistDataAllowed
+                && (mRequestScreenshotAppOps != OP_NONE);
+
+        mCanceled = false;
+        mPendingDataCount = 0;
+        mPendingScreenshotCount = 0;
+        mAssistData.clear();
+        mAssistScreenshot.clear();
+
+        if (fetchData) {
+            if (mAppOpsManager.checkOpNoThrow(mRequestStructureAppOps, callingUid, callingPackage)
+                    == MODE_ALLOWED && allowFetchData) {
+                final int numActivities = activityTokens.size();
+                for (int i = 0; i < numActivities; i++) {
+                    IBinder topActivity = activityTokens.get(i);
+                    try {
+                        MetricsLogger.count(mContext, "assist_with_context", 1);
+                        Bundle receiverExtras = new Bundle();
+                        receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
+                        receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, numActivities);
+                        if (mService.requestAssistContextExtras(ASSIST_CONTEXT_FULL, this,
+                                receiverExtras, topActivity, /* focused= */ i == 0,
+                                    /* newSessionId= */ i == 0)) {
+                            mPendingDataCount++;
+                        } else if (i == 0) {
+                            // Wasn't allowed... given that, let's not do the screenshot either.
+                            if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+                                dispatchAssistDataReceived(null);
+                            } else {
+                                mAssistData.add(null);
+                            }
+                            allowFetchScreenshot = false;
+                            break;
+                        }
+                    } catch (RemoteException e) {
+                        // Can't happen
+                    }
+                }
+            } else {
+                // Wasn't allowed... given that, let's not do the screenshot either.
+                if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+                    dispatchAssistDataReceived(null);
+                } else {
+                    mAssistData.add(null);
+                }
+                allowFetchScreenshot = false;
+            }
+        }
+
+        if (fetchScreenshot) {
+            if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage)
+                    == MODE_ALLOWED && allowFetchScreenshot) {
+                try {
+                    MetricsLogger.count(mContext, "assist_with_screen", 1);
+                    mPendingScreenshotCount++;
+                    mWindowManager.requestAssistScreenshot(this);
+                } catch (RemoteException e) {
+                    // Can't happen
+                }
+            } else {
+                if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+                    dispatchAssistScreenshotReceived(null);
+                } else {
+                    mAssistScreenshot.add(null);
+                }
+            }
+        }
+        // For the cases where we dispatch null data/screenshot due to permissions, just dispatch
+        // request-complete after those are made
+        tryDispatchRequestComplete();
+    }
+
+    /**
+     * This call should only be made when the callbacks are capable of handling the received assist
+     * data. The owner is also responsible for locking before calling this method.
+     */
+    public void processPendingAssistData() {
+        flushPendingAssistData();
+        tryDispatchRequestComplete();
+    }
+
+    private void flushPendingAssistData() {
+        final int dataCount = mAssistData.size();
+        for (int i = 0; i < dataCount; i++) {
+            dispatchAssistDataReceived(mAssistData.get(i));
+        }
+        mAssistData.clear();
+        final int screenshotsCount = mAssistScreenshot.size();
+        for (int i = 0; i < screenshotsCount; i++) {
+            dispatchAssistScreenshotReceived(mAssistScreenshot.get(i));
+        }
+        mAssistScreenshot.clear();
+    }
+
+    public int getPendingDataCount() {
+        return mPendingDataCount;
+    }
+
+    public int getPendingScreenshotCount() {
+        return mPendingScreenshotCount;
+    }
+
+    /**
+     * Cancels the current request for the assist data.
+     */
+    public void cancel() {
+        // Reset the pending data count, if we receive new assist data after this point, it will
+        // be ignored
+        mCanceled = true;
+        mPendingDataCount = 0;
+        mPendingScreenshotCount = 0;
+        mAssistData.clear();
+        mAssistScreenshot.clear();
+    }
+
+    @Override
+    public void onHandleAssistData(Bundle data) {
+        synchronized (mCallbacksLock) {
+            if (mCanceled) {
+                return;
+            }
+            mPendingDataCount--;
+
+            if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+                // Process any pending data and dispatch the new data as well
+                flushPendingAssistData();
+                dispatchAssistDataReceived(data);
+                tryDispatchRequestComplete();
+            } else {
+                // Queue up the data for processing later
+                mAssistData.add(data);
+            }
+        }
+    }
+
+    @Override
+    public void onHandleAssistScreenshot(Bitmap screenshot) {
+        synchronized (mCallbacksLock) {
+            if (mCanceled) {
+                return;
+            }
+            mPendingScreenshotCount--;
+
+            if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+                // Process any pending data and dispatch the new data as well
+                flushPendingAssistData();
+                dispatchAssistScreenshotReceived(screenshot);
+                tryDispatchRequestComplete();
+            } else {
+                // Queue up the data for processing later
+                mAssistScreenshot.add(screenshot);
+            }
+        }
+    }
+
+    private void dispatchAssistDataReceived(Bundle data) {
+        int activityIndex = 0;
+        int activityCount = 0;
+        final Bundle receiverExtras = data != null
+                ? data.getBundle(ASSIST_KEY_RECEIVER_EXTRAS) : null;
+        if (receiverExtras != null) {
+            activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
+            activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
+        }
+        mCallbacks.onAssistDataReceivedLocked(data, activityIndex, activityCount);
+    }
+
+    private void dispatchAssistScreenshotReceived(Bitmap screenshot) {
+        mCallbacks.onAssistScreenshotReceivedLocked(screenshot);
+    }
+
+    private void tryDispatchRequestComplete() {
+        if (mPendingDataCount == 0 && mPendingScreenshotCount == 0 &&
+                mAssistData.isEmpty() && mAssistScreenshot.isEmpty()) {
+            mCallbacks.onAssistRequestCompleted();
+        }
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mPendingDataCount="); pw.println(mPendingDataCount);
+        pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
+        pw.print(prefix); pw.print("mPendingScreenshotCount="); pw.println(mPendingScreenshotCount);
+        pw.print(prefix); pw.print("mAssistScreenshot="); pw.println(mAssistScreenshot);
+    }
+}
\ No newline at end of file
diff --git a/com/android/server/am/BatteryStatsService.java b/com/android/server/am/BatteryStatsService.java
index db12ae2..a035bd0 100644
--- a/com/android/server/am/BatteryStatsService.java
+++ b/com/android/server/am/BatteryStatsService.java
@@ -21,8 +21,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.net.wifi.WifiActivityEnergyInfo;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerSaveState;
 import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -52,7 +54,6 @@
 import com.android.internal.os.RpmStats;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
 import android.util.StatsLog;
 
 import java.io.File;
@@ -177,9 +178,22 @@
     }
 
     public void publish() {
+        LocalServices.addService(BatteryStatsInternal.class, new LocalService());
         ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
     }
 
+    private final class LocalService extends BatteryStatsInternal {
+        @Override
+        public String[] getWifiIfaces() {
+            return mStats.getWifiIfaces().clone();
+        }
+
+        @Override
+        public String[] getMobileIfaces() {
+            return mStats.getMobileIfaces().clone();
+        }
+    }
+
     private static void awaitUninterruptibly(Future<?> future) {
         while (true) {
             try {
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index c3fed17..76b4679 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -41,10 +41,8 @@
 import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
-
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.wm.WindowManagerService;
-
 import java.io.PrintWriter;
 
 /**
@@ -236,24 +234,33 @@
         final ActivityRecord lastDismissingKeyguardActivity = mDismissingKeyguardActivity;
         mOccluded = false;
         mDismissingKeyguardActivity = null;
-        final ActivityDisplay display = mStackSupervisor.getDefaultDisplay();
-        for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = display.getChildAt(stackNdx);
 
-            // Only the focused stack top activity may control occluded state
-            if (mStackSupervisor.isFocusedStack(stack)) {
+        for (int displayNdx = mStackSupervisor.getChildCount() - 1; displayNdx >= 0; displayNdx--) {
+            final ActivityDisplay display = mStackSupervisor.getChildAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
 
-                // A dismissing activity occludes Keyguard in the insecure case for legacy reasons.
-                final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
-                mOccluded = stack.topActivityOccludesKeyguard()
-                        || (topDismissing != null
-                                && stack.topRunningActivityLocked() == topDismissing
-                                && canShowWhileOccluded(true /* dismissKeyguard */,
-                                        false /* showWhenLocked */));
-            }
-            if (mDismissingKeyguardActivity == null
-                    && stack.getTopDismissingKeyguardActivity() != null) {
-                mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
+                // Only the top activity of the focused stack on the default display may control
+                // occluded state.
+                if (display.mDisplayId == DEFAULT_DISPLAY
+                        && mStackSupervisor.isFocusedStack(stack)) {
+
+                    // A dismissing activity occludes Keyguard in the insecure case for legacy
+                    // reasons.
+                    final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
+                    mOccluded =
+                            stack.topActivityOccludesKeyguard()
+                                    || (topDismissing != null
+                                            && stack.topRunningActivityLocked() == topDismissing
+                                            && canShowWhileOccluded(
+                                                    true /* dismissKeyguard */,
+                                                    false /* showWhenLocked */));
+                }
+
+                if (mDismissingKeyguardActivity == null
+                        && stack.getTopDismissingKeyguardActivity() != null) {
+                    mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
+                }
             }
         }
         mOccluded |= mWindowManager.isShowingDream();
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index 4b2a084..e87b4e6 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -143,9 +143,17 @@
     LockTaskNotify mLockTaskNotify;
 
     /**
-     * The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks
-     * may be finished until there is only one entry left. If this is empty the system is not
-     * in lockTask mode.
+     * The chain of tasks in LockTask mode, in the order of when they first entered LockTask mode.
+     *
+     * The first task in the list, which started the current LockTask session, is called the root
+     * task. It coincides with the Home task in a typical multi-app kiosk deployment. When there are
+     * more than one locked tasks, the root task can't be finished. Nor can it be moved to the back
+     * of the stack by {@link ActivityStack#moveTaskToBackLocked(int)};
+     *
+     * Calling {@link Activity#stopLockTask()} on the root task will finish all tasks but itself in
+     * this list, and the device will exit LockTask mode.
+     *
+     * The list is empty if LockTask is inactive.
      */
     private final ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>();
 
@@ -164,7 +172,7 @@
      * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
      * {@link ActivityManager#LOCK_TASK_MODE_PINNED}
      */
-    private int mLockTaskModeState;
+    private int mLockTaskModeState = LOCK_TASK_MODE_NONE;
 
     /**
      * This is ActivityStackSupervisor's Handler.
@@ -199,8 +207,29 @@
      * @return whether the given task is locked at the moment. Locked tasks cannot be moved to the
      * back of the stack.
      */
-    boolean checkLockedTask(TaskRecord task) {
-        if (mLockTaskModeTasks.contains(task)) {
+    @VisibleForTesting
+    boolean isTaskLocked(TaskRecord task) {
+        return mLockTaskModeTasks.contains(task);
+    }
+
+    /**
+     * @return {@code true} whether this task first started the current LockTask session.
+     */
+    private boolean isRootTask(TaskRecord task) {
+        return mLockTaskModeTasks.indexOf(task) == 0;
+    }
+
+    /**
+     * @return whether the given activity is blocked from finishing, because it is the only activity
+     * of the last locked task and finishing it would mean that lock task mode is ended illegally.
+     */
+    boolean activityBlockedFromFinish(ActivityRecord activity) {
+        final TaskRecord task = activity.getTask();
+        if (activity == task.getRootActivity()
+                && activity == task.getTopActivity()
+                && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
+                && isRootTask(task)) {
+            Slog.i(TAG, "Not finishing task in lock task mode");
             showLockTaskToast();
             return true;
         }
@@ -208,20 +237,16 @@
     }
 
     /**
-     * @return whether the given activity is blocked from finishing, because it is the root activity
-     * of the last locked task and finishing it would mean that lock task mode is ended illegally.
+     * @return whether the given task can be moved to the back of the stack with
+     * {@link ActivityStack#moveTaskToBackLocked(int)}
+     * @see #mLockTaskModeTasks
      */
-    boolean activityBlockedFromFinish(ActivityRecord activity) {
-        TaskRecord task = activity.getTask();
-        if (activity == task.getRootActivity()
-                && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
-                && mLockTaskModeTasks.size() == 1
-                && mLockTaskModeTasks.contains(task)) {
-            Slog.i(TAG, "Not finishing task in lock task mode");
+    boolean canMoveTaskToBack(TaskRecord task) {
+        if (isRootTask(task)) {
             showLockTaskToast();
-            return true;
+            return false;
         }
-        return false;
+        return true;
     }
 
     /**
@@ -246,7 +271,7 @@
     private boolean isLockTaskModeViolationInternal(TaskRecord task, boolean isNewClearTask) {
         // TODO: Double check what's going on here. If the task is already in lock task mode, it's
         // likely whitelisted, so will return false below.
-        if (getLockedTask() == task && !isNewClearTask) {
+        if (isTaskLocked(task) && !isNewClearTask) {
             // If the task is already at the top and won't be cleared, then allow the operation
             return false;
         }
@@ -270,80 +295,116 @@
     /**
      * Stop the current lock task mode.
      *
-     * @param isSystemInitiated indicates whether this request was initiated by the system via
-     *                          {@link ActivityManagerService#stopSystemLockTaskMode()}.
+     * This is called by {@link ActivityManagerService} and performs various checks before actually
+     * finishing the locked task.
+     *
+     * @param task the task that requested the end of lock task mode ({@code null} for quitting app
+     *             pinning mode)
+     * @param isSystemCaller indicates whether this request comes from the system via
+     *                       {@link ActivityManagerService#stopSystemLockTaskMode()}. If
+     *                       {@code true}, it means the user intends to stop pinned mode through UI;
+     *                       otherwise, it's called by an app and we need to stop locked or pinned
+     *                       mode, subject to checks.
      * @param callingUid the caller that requested the end of lock task mode.
+     * @throws IllegalArgumentException if the calling task is invalid (e.g., {@code null} or not in
+     *                                  foreground)
      * @throws SecurityException if the caller is not authorized to stop the lock task mode, i.e. if
      *                           they differ from the one that launched lock task mode.
      */
-    void stopLockTaskMode(boolean isSystemInitiated, int callingUid) {
-        final TaskRecord lockTask = getLockedTask();
-        if (lockTask == null || mLockTaskModeState == LOCK_TASK_MODE_NONE) {
-            // Our work here is done.
+    void stopLockTaskMode(@Nullable TaskRecord task, boolean isSystemCaller, int callingUid) {
+        if (mLockTaskModeState == LOCK_TASK_MODE_NONE) {
             return;
         }
 
-        if (isSystemInitiated && mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
-            // As system can only start app pinning, we also only let it unlock in this mode.
-            showLockTaskToast();
+        if (isSystemCaller) {
+            if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                clearLockedTasks("stopAppPinning");
+            } else {
+                Slog.e(TAG_LOCKTASK, "Attempted to stop LockTask with isSystemCaller=true");
+                showLockTaskToast();
+            }
+
+        } else {
+            // Ensure calling activity is not null
+            if (task == null) {
+                throw new IllegalArgumentException("can't stop LockTask for null task");
+            }
+
+            // Ensure the same caller for startLockTaskMode and stopLockTaskMode.
+            // It is possible lockTaskMode was started by the system process because
+            // android:lockTaskMode is set to a locking value in the application manifest
+            // instead of the app calling startLockTaskMode. In this case
+            // {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the
+            // {@link TaskRecord.effectiveUid} instead. Also caller with
+            // {@link MANAGE_ACTIVITY_STACKS} can stop any lock task.
+            if (callingUid != task.mLockTaskUid
+                    && (task.mLockTaskUid != 0 || callingUid != task.effectiveUid)) {
+                throw new SecurityException("Invalid uid, expected " + task.mLockTaskUid
+                        + " callingUid=" + callingUid + " effectiveUid=" + task.effectiveUid);
+            }
+
+            // We don't care if it's pinned or locked mode; this will stop it anyways.
+            clearLockedTask(task);
+        }
+    }
+
+    /**
+     * Clear all locked tasks and request the end of LockTask mode.
+     *
+     * This method is called by {@link UserController} when starting a new foreground user, and,
+     * unlike {@link #stopLockTaskMode(TaskRecord, boolean, int)}, it doesn't perform the checks.
+     */
+    void clearLockedTasks(String reason) {
+        if (DEBUG_LOCKTASK) Slog.i(TAG_LOCKTASK, "clearLockedTasks: " + reason);
+        if (!mLockTaskModeTasks.isEmpty()) {
+            clearLockedTask(mLockTaskModeTasks.get(0));
+        }
+    }
+
+    /**
+     * Clear one locked task from LockTask mode.
+     *
+     * If the requested task is the root task (see {@link #mLockTaskModeTasks}), then all locked
+     * tasks are cleared. Otherwise, only the requested task is cleared. LockTask mode is stopped
+     * when the last locked task is cleared.
+     *
+     * @param task the task to be cleared from LockTask mode.
+     */
+    void clearLockedTask(final TaskRecord task) {
+        if (task == null || mLockTaskModeTasks.isEmpty()) return;
+
+        if (task == mLockTaskModeTasks.get(0)) {
+            // We're removing the root task while there are other locked tasks. Therefore we should
+            // clear all locked tasks in reverse order.
+            for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx > 0; --taskNdx) {
+                clearLockedTask(mLockTaskModeTasks.get(taskNdx));
+            }
+        }
+
+        removeLockedTask(task);
+        if (mLockTaskModeTasks.isEmpty()) {
             return;
         }
-
-        // Ensure the same caller for startLockTaskMode and stopLockTaskMode.
-        // It is possible lockTaskMode was started by the system process because
-        // android:lockTaskMode is set to a locking value in the application manifest
-        // instead of the app calling startLockTaskMode. In this case
-        // {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the
-        // {@link TaskRecord.effectiveUid} instead. Also caller with
-        // {@link MANAGE_ACTIVITY_STACKS} can stop any lock task.
-        if (!isSystemInitiated && callingUid != lockTask.mLockTaskUid
-                && (lockTask.mLockTaskUid != 0 || callingUid != lockTask.effectiveUid)) {
-            throw new SecurityException("Invalid uid, expected " + lockTask.mLockTaskUid
-                    + " callingUid=" + callingUid + " effectiveUid=" + lockTask.effectiveUid);
-        }
-
-        clearLockTaskMode("stopLockTask");
+        task.performClearTaskLocked();
+        mSupervisor.resumeFocusedStackTopActivityLocked();
     }
 
     /**
      * Remove the given task from the locked task list. If this was the last task in the list,
      * lock task mode is stopped.
      */
-    void removeLockedTask(final TaskRecord task) {
+    private void removeLockedTask(final TaskRecord task) {
         if (!mLockTaskModeTasks.remove(task)) {
             return;
         }
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "removeLockedTask: removed " + task);
+        if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: removed " + task);
         if (mLockTaskModeTasks.isEmpty()) {
-            // Last one.
             if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task +
                     " last task, reverting locktask mode. Callers=" + Debug.getCallers(3));
             mHandler.post(() -> performStopLockTask(task.userId));
         }
     }
 
-    /**
-     * Remove the topmost task from the locked task list. If this is the last task in the list, it
-     * will result in the end of locked task mode.
-     */
-    void clearLockTaskMode(String reason) {
-        // Take out of lock task mode if necessary
-        final TaskRecord lockedTask = getLockedTask();
-        if (lockedTask != null) {
-            removeLockedTask(lockedTask);
-            if (!mLockTaskModeTasks.isEmpty()) {
-                // There are locked tasks remaining, can only finish this task, not unlock it.
-                if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
-                        "setLockTaskMode: Tasks remaining, can't unlock");
-                lockedTask.performClearTaskLocked();
-                mSupervisor.resumeFocusedStackTopActivityLocked();
-                return;
-            }
-        }
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
-                "setLockTaskMode: No tasks to unlock. Callers=" + Debug.getCallers(4));
-    }
-
     // This method should only be called on the handler thread
     private void performStopLockTask(int userId) {
         // When lock task ends, we enable the status bars.
@@ -382,17 +443,18 @@
      * Method to start lock task mode on a given task.
      *
      * @param task the task that should be locked.
-     * @param isSystemInitiated indicates whether this request was initiated by the system via
-     *                          {@link ActivityManagerService#startSystemLockTaskMode(int)}.
+     * @param isSystemCaller indicates whether this request was initiated by the system via
+     *                       {@link ActivityManagerService#startSystemLockTaskMode(int)}. If
+     *                       {@code true}, this intends to start pinned mode; otherwise, we look
+     *                       at the calling task's mLockTaskAuth to decide which mode to start.
      * @param callingUid the caller that requested the launch of lock task mode.
      */
-    void startLockTaskMode(@NonNull TaskRecord task, boolean isSystemInitiated,
-            int callingUid) {
-        if (!isSystemInitiated) {
+    void startLockTaskMode(@NonNull TaskRecord task, boolean isSystemCaller, int callingUid) {
+        if (!isSystemCaller) {
             task.mLockTaskUid = callingUid;
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
                 // startLockTask() called by app, but app is not part of lock task whitelist. Show
-                // app pinning request. We will come back here with isSystemInitiated true.
+                // app pinning request. We will come back here with isSystemCaller true.
                 if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Mode default, asking user");
                 StatusBarManagerInternal statusBarManager = LocalServices.getService(
                         StatusBarManagerInternal.class);
@@ -404,8 +466,9 @@
         }
 
         // System can only initiate screen pinning, not full lock task mode
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, isSystemInitiated ? "Locking pinned" : "Locking fully");
-        setLockTaskMode(task, isSystemInitiated ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
+        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
+                isSystemCaller ? "Locking pinned" : "Locking fully");
+        setLockTaskMode(task, isSystemCaller ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
                 "startLockTask", true);
     }
 
@@ -434,12 +497,12 @@
                     task.userId,
                     lockTaskModeState));
         }
-
-        // Add it or move it to the top.
         if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskMode: Locking to " + task +
                 " Callers=" + Debug.getCallers(4));
-        mLockTaskModeTasks.remove(task);
-        mLockTaskModeTasks.add(task);
+
+        if (!mLockTaskModeTasks.contains(task)) {
+            mLockTaskModeTasks.add(task);
+        }
 
         if (task.mLockTaskUid == -1) {
             task.mLockTaskUid = task.effectiveUid;
@@ -556,8 +619,7 @@
         }
 
         mLockTaskFeatures.put(userId, flags);
-        TaskRecord lockedTask = getLockedTask();
-        if (lockedTask != null && userId == lockedTask.userId) {
+        if (!mLockTaskModeTasks.isEmpty() && userId == mLockTaskModeTasks.get(0).userId) {
             mHandler.post(() -> {
                 if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
                     setStatusBarState(mLockTaskModeState, userId);
@@ -672,17 +734,6 @@
         return mLockTaskFeatures.get(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
     }
 
-    /**
-     * @return the topmost locked task
-     */
-    private TaskRecord getLockedTask() {
-        final int top = mLockTaskModeTasks.size() - 1;
-        if (top >= 0) {
-            return mLockTaskModeTasks.get(top);
-        }
-        return null;
-    }
-
     // Should only be called on the handler thread
     @Nullable
     private IStatusBarService getStatusBarService() {
diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java
index 0b9e0a2..d35c37b 100644
--- a/com/android/server/am/RecentTasks.java
+++ b/com/android/server/am/RecentTasks.java
@@ -264,6 +264,20 @@
         return cn.equals(mRecentsComponent) && UserHandle.isSameApp(uid, mRecentsUid);
     }
 
+    /**
+     * @return the recents component.
+     */
+    ComponentName getRecentsComponent() {
+        return mRecentsComponent;
+    }
+
+    /**
+     * @return the uid for the recents component.
+     */
+    int getRecentsComponentUid() {
+        return mRecentsUid;
+    }
+
     void registerCallback(Callbacks callback) {
         mCallbacks.add(callback);
     }
diff --git a/com/android/server/am/RunningTasks.java b/com/android/server/am/RunningTasks.java
index 400b03a..c860df8 100644
--- a/com/android/server/am/RunningTasks.java
+++ b/com/android/server/am/RunningTasks.java
@@ -47,8 +47,10 @@
     void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType,
             @WindowingMode int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
             int callingUid, boolean allowed) {
-        // For each stack on each display, add the tasks into the sorted set and then pull the first
-        // {@param maxNum} from the set
+        // Return early if there are no tasks to fetch
+        if (maxNum <= 0) {
+            return;
+        }
 
         // Gather all of the tasks across all of the tasks, and add them to the sorted set
         mTmpSortedSet.clear();
diff --git a/com/android/server/am/TaskChangeNotificationController.java b/com/android/server/am/TaskChangeNotificationController.java
index 5a7e7ce..7896e2d 100644
--- a/com/android/server/am/TaskChangeNotificationController.java
+++ b/com/android/server/am/TaskChangeNotificationController.java
@@ -95,8 +95,8 @@
     };
 
     private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
-        final ActivityRecord r = (ActivityRecord) m.obj;
-        l.onActivityPinned(r.packageName, r.userId, r.getTask().taskId, r.getStackId());
+        l.onActivityPinned((String) m.obj /* packageName */, m.sendingUid /* userId */,
+                m.arg1 /* taskId */, m.arg2 /* stackId */);
     };
 
     private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
@@ -281,7 +281,9 @@
     /** Notifies all listeners when an Activity is pinned. */
     void notifyActivityPinned(ActivityRecord r) {
         mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
-        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG, r);
+        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
+                r.getTask().taskId, r.getStackId(), r.packageName);
+        msg.sendingUid = r.userId;
         forAllLocalListeners(mNotifyActivityPinned, msg);
         msg.sendToTarget();
     }
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 1b5a1ce..949f51f 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -438,7 +438,7 @@
     }
 
     void removeWindowContainer() {
-        mService.mLockTaskController.removeLockedTask(this);
+        mService.mLockTaskController.clearLockedTask(this);
         mWindowContainerController.removeContainer();
         if (!getWindowConfiguration().persistTaskBounds()) {
             // Reset current bounds for task whose bounds shouldn't be persisted so it uses
@@ -606,9 +606,8 @@
         final int toStackWindowingMode = toStack.getWindowingMode();
         final ActivityRecord topActivity = getTopActivity();
 
-        final boolean mightReplaceWindow =
-                replaceWindowsOnTaskMove(getWindowingMode(), toStackWindowingMode)
-                        && topActivity != null;
+        final boolean mightReplaceWindow = topActivity != null
+                && replaceWindowsOnTaskMove(getWindowingMode(), toStackWindowingMode);
         if (mightReplaceWindow) {
             // We are about to relaunch the activity because its configuration changed due to
             // being maximized, i.e. size change. The activity will first remove the old window
@@ -722,7 +721,6 @@
         }
 
         // TODO: Handle incorrect request to move before the actual move, not after.
-        final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenPrimaryStack();
         supervisor.handleNonResizableTaskIfNeeded(this, preferredStack.getWindowingMode(),
                 DEFAULT_DISPLAY, toStack);
 
@@ -735,10 +733,9 @@
     }
 
     /**
-     * Returns true if the windows of tasks being moved to the target stack from the source
-     * stack should be replaced, meaning that window manager will keep the old window around
-     * until the new is ready.
-     * @hide
+     * @return True if the windows of tasks being moved to the target stack from the source stack
+     * should be replaced, meaning that window manager will keep the old window around until the new
+     * is ready.
      */
     private static boolean replaceWindowsOnTaskMove(
             int sourceWindowingMode, int targetWindowingMode) {
@@ -1036,6 +1033,16 @@
         return null;
     }
 
+    boolean isVisible() {
+        for (int i = mActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord r = mActivities.get(i);
+            if (r.visible) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
         if (mStack != null) {
             for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
@@ -1129,6 +1136,9 @@
 
         mActivities.remove(newTop);
         mActivities.add(newTop);
+
+        // Make sure window manager is aware of the position change.
+        mWindowContainerController.positionChildAtTop(newTop.mWindowContainerController);
         updateEffectiveIntent();
 
         setFrontOfTask();
@@ -2049,11 +2059,8 @@
     }
 
     static Rect validateBounds(Rect bounds) {
-        if (bounds != null && bounds.isEmpty()) {
-            Slog.wtf(TAG, "Received strange task bounds: " + bounds, new Throwable());
-            return null;
-        }
-        return bounds;
+        // TODO: Not needed once we have bounds in WindowConfiguration.
+        return (bounds != null && bounds.isEmpty()) ? null : bounds;
     }
 
     /** Updates the task's bounds and override configuration to match what is expected for the
@@ -2082,7 +2089,7 @@
     }
 
     /** Returns the bounds that should be used to launch this task. */
-    private Rect getLaunchBounds() {
+    Rect getLaunchBounds() {
         if (mStack == null) {
             return null;
         }
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index 44f83b0..2df5dc9 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -872,9 +872,7 @@
             }
 
             if (foreground) {
-                // TODO: I don't think this does what the caller think it does. Seems to only
-                // remove one locked task and won't work if multiple locked tasks are present.
-                mInjector.clearLockTaskMode("startUser");
+                mInjector.clearAllLockedTasks("startUser");
             }
 
             final UserInfo userInfo = getUserInfo(userId);
@@ -2053,9 +2051,9 @@
             }
         }
 
-        protected void clearLockTaskMode(String reason) {
+        protected void clearAllLockedTasks(String reason) {
             synchronized (mService) {
-                mService.mLockTaskController.clearLockTaskMode(reason);
+                mService.mLockTaskController.clearLockedTasks(reason);
             }
         }
     }
diff --git a/com/android/server/audio/AudioService.java b/com/android/server/audio/AudioService.java
index 5eb2a8d..15a418d 100644
--- a/com/android/server/audio/AudioService.java
+++ b/com/android/server/audio/AudioService.java
@@ -122,6 +122,7 @@
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
@@ -398,8 +399,9 @@
      * {@link AudioManager#RINGER_MODE_SILENT}, or
      * {@link AudioManager#RINGER_MODE_VIBRATE}.
      */
-    // protected by mSettingsLock
+    @GuardedBy("mSettingsLock")
     private int mRingerMode;  // internal ringer mode, affects muting of underlying streams
+    @GuardedBy("mSettingsLock")
     private int mRingerModeExternal = -1;  // reported ringer mode to outside clients (AudioManager)
 
     /** @see System#MODE_RINGER_STREAMS_AFFECTED */
@@ -929,8 +931,11 @@
         mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
                 "onAudioServerDied"));
         AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
-        final int forSys = mCameraSoundForced ?
-                AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+        final int forSys;
+        synchronized (mSettingsLock) {
+            forSys = mCameraSoundForced ?
+                    AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+        }
         mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
                 "onAudioServerDied"));
         AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
@@ -1340,8 +1345,9 @@
         } else {
             final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
             final boolean activeForReal;
-            if (maybeActiveStreamType == AudioSystem.STREAM_MUSIC) {
-                activeForReal = isAfMusicActiveRecently(0);
+            if (maybeActiveStreamType == AudioSystem.STREAM_RING
+                    || maybeActiveStreamType == AudioSystem.STREAM_NOTIFICATION) {
+                activeForReal = wasStreamActiveRecently(maybeActiveStreamType, 0);
             } else {
                 activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0);
             }
@@ -3797,6 +3803,7 @@
         return (mRingerModeMutedStreams & (1 << streamType)) != 0;
     }
 
+    @GuardedBy("mSettingsLock")
     private boolean updateRingerModeAffectedStreams() {
         int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver,
                 Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -3810,12 +3817,10 @@
             ringerModeAffectedStreams = mRingerModeDelegate
                     .getRingerModeAffectedStreams(ringerModeAffectedStreams);
         }
-        synchronized (mCameraSoundForced) {
-            if (mCameraSoundForced) {
-                ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
-            } else {
-                ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
-            }
+        if (mCameraSoundForced) {
+            ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+        } else {
+            ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
         }
         if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) {
             ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
@@ -3879,13 +3884,13 @@
 
     /**
      * For code clarity for getActiveStreamType(int)
-     * @param delay_ms max time since last STREAM_MUSIC activity to consider
-     * @return true if STREAM_MUSIC is active in streams handled by AudioFlinger now or
+     * @param delay_ms max time since last stream activity to consider
+     * @return true if stream is active in streams handled by AudioFlinger now or
      *     in the last "delay_ms" ms.
      */
-    private boolean isAfMusicActiveRecently(int delay_ms) {
-        return AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, delay_ms)
-                || AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, delay_ms);
+    private boolean wasStreamActiveRecently(int stream, int delay_ms) {
+        return AudioSystem.isStreamActive(stream, delay_ms)
+                || AudioSystem.isStreamActiveRemotely(stream, delay_ms);
     }
 
     private int getActiveStreamType(int suggestedStreamType) {
@@ -3906,21 +3911,30 @@
                     return AudioSystem.STREAM_VOICE_CALL;
                 }
             } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
-                if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) {
+                if (wasStreamActiveRecently(AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
                     if (DEBUG_VOL)
-                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
+                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING stream active");
+                    return AudioSystem.STREAM_RING;
+                } else if (wasStreamActiveRecently(
+                        AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
+                    if (DEBUG_VOL)
+                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
+                    return AudioSystem.STREAM_NOTIFICATION;
+                } else {
+                    if (DEBUG_VOL)
+                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC b/c default");
                     return AudioSystem.STREAM_MUSIC;
-                    } else {
-                        if (DEBUG_VOL)
-                            Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default");
-                        return AudioSystem.STREAM_RING;
                 }
-            } else if (isAfMusicActiveRecently(0)) {
+            } else if (
+                    wasStreamActiveRecently(AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
                 if (DEBUG_VOL)
-                    Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
-                return AudioSystem.STREAM_MUSIC;
+                    Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
+                return AudioSystem.STREAM_NOTIFICATION;
+            } else if (wasStreamActiveRecently(AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
+                if (DEBUG_VOL)
+                    Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING stream active");
+                return AudioSystem.STREAM_RING;
             }
-            break;
         default:
             if (isInCommunication()) {
                 if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
@@ -3931,20 +3945,26 @@
                     if (DEBUG_VOL)  Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL");
                     return AudioSystem.STREAM_VOICE_CALL;
                 }
-            } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION,
-                    sStreamOverrideDelayMs) ||
-                    AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
-                            sStreamOverrideDelayMs)) {
+            } else if (AudioSystem.isStreamActive(
+                    AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
                 if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
                 return AudioSystem.STREAM_NOTIFICATION;
+            } else if (AudioSystem.isStreamActive(
+                    AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
+                if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING");
+                return AudioSystem.STREAM_RING;
             } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
-                if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) {
-                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
-                    return AudioSystem.STREAM_MUSIC;
-                } else {
-                    if (DEBUG_VOL) Log.v(TAG,
-                            "getActiveStreamType: using STREAM_NOTIFICATION as default");
+                if (AudioSystem.isStreamActive(
+                        AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
+                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
                     return AudioSystem.STREAM_NOTIFICATION;
+                } else if (AudioSystem.isStreamActive(
+                        AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
+                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING");
+                    return AudioSystem.STREAM_RING;
+                } else {
+                    if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default");
+                    return AudioSystem.STREAM_MUSIC;
                 }
             }
             break;
@@ -4185,7 +4205,6 @@
     //  2   mSetModeDeathHandlers
     //  3     mSettingsLock
     //  4       VolumeStreamState.class
-    //  5         mCameraSoundForced
     public class VolumeStreamState {
         private final int mStreamType;
         private final int mIndexMin;
@@ -4252,27 +4271,28 @@
         }
 
         public void readSettings() {
-            synchronized (VolumeStreamState.class) {
-                // force maximum volume on all streams if fixed volume property is set
-                if (mUseFixedVolume) {
-                    mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
-                    return;
-                }
-                // do not read system stream volume from settings: this stream is always aliased
-                // to another stream type and its volume is never persisted. Values in settings can
-                // only be stale values
-                if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
-                        (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
-                    int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
-                    synchronized (mCameraSoundForced) {
+            synchronized (mSettingsLock) {
+                synchronized (VolumeStreamState.class) {
+                    // force maximum volume on all streams if fixed volume property is set
+                    if (mUseFixedVolume) {
+                        mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+                        return;
+                    }
+                    // do not read system stream volume from settings: this stream is always aliased
+                    // to another stream type and its volume is never persisted. Values in settings can
+                    // only be stale values
+                    if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
+                            (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
+                        int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
                         if (mCameraSoundForced) {
                             index = mIndexMax;
                         }
+                        mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+                        return;
                     }
-                    mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
-                    return;
                 }
-
+            }
+            synchronized (VolumeStreamState.class) {
                 int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
 
                 for (int i = 0; remainingDevices != 0; i++) {
@@ -4385,34 +4405,34 @@
         public boolean setIndex(int index, int device, String caller) {
             boolean changed = false;
             int oldIndex;
-            synchronized (VolumeStreamState.class) {
-                oldIndex = getIndex(device);
-                index = getValidIndex(index);
-                synchronized (mCameraSoundForced) {
+            synchronized (mSettingsLock) {
+                synchronized (VolumeStreamState.class) {
+                    oldIndex = getIndex(device);
+                    index = getValidIndex(index);
                     if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
                         index = mIndexMax;
                     }
-                }
-                mIndexMap.put(device, index);
+                    mIndexMap.put(device, index);
 
-                changed = oldIndex != index;
-                // Apply change to all streams using this one as alias if:
-                // - the index actually changed OR
-                // - there is no volume index stored for this device on alias stream.
-                // If changing volume of current device, also change volume of current
-                // device on aliased stream
-                final boolean currentDevice = (device == getDeviceForStream(mStreamType));
-                final int numStreamTypes = AudioSystem.getNumStreamTypes();
-                for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
-                    final VolumeStreamState aliasStreamState = mStreamStates[streamType];
-                    if (streamType != mStreamType &&
-                            mStreamVolumeAlias[streamType] == mStreamType &&
-                            (changed || !aliasStreamState.hasIndexForDevice(device))) {
-                        final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
-                        aliasStreamState.setIndex(scaledIndex, device, caller);
-                        if (currentDevice) {
-                            aliasStreamState.setIndex(scaledIndex,
-                                    getDeviceForStream(streamType), caller);
+                    changed = oldIndex != index;
+                    // Apply change to all streams using this one as alias if:
+                    // - the index actually changed OR
+                    // - there is no volume index stored for this device on alias stream.
+                    // If changing volume of current device, also change volume of current
+                    // device on aliased stream
+                    final boolean currentDevice = (device == getDeviceForStream(mStreamType));
+                    final int numStreamTypes = AudioSystem.getNumStreamTypes();
+                    for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+                        final VolumeStreamState aliasStreamState = mStreamStates[streamType];
+                        if (streamType != mStreamType &&
+                                mStreamVolumeAlias[streamType] == mStreamType &&
+                                (changed || !aliasStreamState.hasIndexForDevice(device))) {
+                            final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+                            aliasStreamState.setIndex(scaledIndex, device, caller);
+                            if (currentDevice) {
+                                aliasStreamState.setIndex(scaledIndex,
+                                        getDeviceForStream(streamType), caller);
+                            }
                         }
                     }
                 }
@@ -6022,13 +6042,8 @@
 
             boolean cameraSoundForced = readCameraSoundForced();
             synchronized (mSettingsLock) {
-                boolean cameraSoundForcedChanged = false;
-                synchronized (mCameraSoundForced) {
-                    if (cameraSoundForced != mCameraSoundForced) {
-                        mCameraSoundForced = cameraSoundForced;
-                        cameraSoundForcedChanged = true;
-                    }
-                }
+                final boolean cameraSoundForcedChanged = (cameraSoundForced != mCameraSoundForced);
+                mCameraSoundForced = cameraSoundForced;
                 if (cameraSoundForcedChanged) {
                     if (!mIsSingleVolume) {
                         VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
@@ -6350,10 +6365,10 @@
     //   stream override timeout when adjusting volume
     //---------------------------------------------------------------------------------
 
-    // AudioService.getActiveStreamType() will return:
     // - STREAM_NOTIFICATION on tablets during this period after a notification stopped
-    // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt
-    // stopped
+    // - STREAM_RING on phones during this period after a notification stopped
+    // - STREAM_MUSIC otherwise
+
     private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 0;
     private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000;
 
@@ -6408,11 +6423,12 @@
     //==========================================================================================
 
     // cached value of com.android.internal.R.bool.config_camera_sound_forced
-    private Boolean mCameraSoundForced;
+    @GuardedBy("mSettingsLock")
+    private boolean mCameraSoundForced;
 
     // called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound
     public boolean isCameraSoundForced() {
-        synchronized (mCameraSoundForced) {
+        synchronized (mSettingsLock) {
             return mCameraSoundForced;
         }
     }
@@ -6734,7 +6750,9 @@
         public void setRingerModeDelegate(RingerModeDelegate delegate) {
             mRingerModeDelegate = delegate;
             if (mRingerModeDelegate != null) {
-                updateRingerModeAffectedStreams();
+                synchronized (mSettingsLock) {
+                    updateRingerModeAffectedStreams();
+                }
                 setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate");
             }
         }
diff --git a/com/android/server/autofill/AutofillManagerService.java b/com/android/server/autofill/AutofillManagerService.java
index 6c3eb20..23e4f50 100644
--- a/com/android/server/autofill/AutofillManagerService.java
+++ b/com/android/server/autofill/AutofillManagerService.java
@@ -447,7 +447,6 @@
         android.view.autofill.Helper.sDebug = debug;
     }
 
-
     private void setVerboseLocked(boolean verbose) {
         com.android.server.autofill.Helper.sVerbose = verbose;
         android.view.autofill.Helper.sVerbose = verbose;
@@ -513,6 +512,16 @@
         }
 
         @Override
+        public void removeClient(IAutoFillManagerClient client, int userId) {
+            synchronized (mLock) {
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+                if (service != null) {
+                    service.removeClientLocked(client);
+                }
+            }
+        }
+
+        @Override
         public void setAuthenticationResult(Bundle data, int sessionId, int authenticationId,
                 int userId) {
             synchronized (mLock) {
diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java
index 2ed5eee..21e2722 100644
--- a/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -273,6 +273,12 @@
         return isEnabled();
     }
 
+    void removeClientLocked(IAutoFillManagerClient client) {
+        if (mClients != null) {
+            mClients.unregister(client);
+        }
+    }
+
     void setAuthenticationResultLocked(Bundle data, int sessionId, int authenticationId, int uid) {
         if (!isEnabled()) {
             return;
@@ -548,6 +554,10 @@
         }
 
         sendStateToClients(true);
+        if (mClients != null) {
+            mClients.kill();
+            mClients = null;
+        }
     }
 
     @NonNull
@@ -602,7 +612,7 @@
             if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
-                                null, null, null, null));
+                                null, null, null, null, null, -1));
             }
         }
     }
@@ -616,7 +626,7 @@
             if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
-                                clientState, null, null, null, null, null, null));
+                                clientState, null, null, null, null, null, null, null, -1));
             }
         }
     }
@@ -628,7 +638,7 @@
         synchronized (mLock) {
             if (isValidEventLocked("logSaveShown()", sessionId)) {
                 mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
-                        null, null, null, null, null));
+                        null, null, null, null, null, null, -1));
             }
         }
     }
@@ -642,7 +652,7 @@
             if (isValidEventLocked("logDatasetSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
-                                null, null, null, null, null));
+                                null, null, null, null, null, null, -1));
             }
         }
     }
@@ -656,13 +666,15 @@
             @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) {
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
                 mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
                         clientState, selectedDatasets, ignoredDatasets,
                         changedFieldIds, changedDatasetIds,
-                        manuallyFilledFieldIds, manuallyFilledDatasetIds));
+                        manuallyFilledFieldIds, manuallyFilledDatasetIds,
+                        detectedRemoteId, detectedFieldScore));
             }
         }
     }
@@ -695,6 +707,7 @@
         pw.print(prefix); pw.print("Default component: ");
             pw.println(mContext.getString(R.string.config_defaultAutofillService));
         pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
+        pw.print(prefix); pw.print("Field detection: "); pw.println(isFieldDetectionEnabled());
         pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
         pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
 
@@ -749,6 +762,9 @@
             }
         }
 
+        pw.print(prefix); pw.println("Clients");
+        mClients.dump(pw, prefix2);
+
         if (mEventHistory == null || mEventHistory.getEvents() == null
                 || mEventHistory.getEvents().size() == 0) {
             pw.print(prefix); pw.println("No event on last fill response");
@@ -813,7 +829,23 @@
                     synchronized (mLock) {
                         resetSession = resetClient || isClientSessionDestroyedLocked(client);
                     }
-                    client.setState(isEnabled(), resetSession, resetClient);
+                    int flags = 0;
+                    if (isEnabled()) {
+                        flags |= AutofillManager.SET_STATE_FLAG_ENABLED;
+                    }
+                    if (resetSession) {
+                        flags |= AutofillManager.SET_STATE_FLAG_RESET_SESSION;
+                    }
+                    if (resetClient) {
+                        flags |= AutofillManager.SET_STATE_FLAG_RESET_CLIENT;
+                    }
+                    if (sDebug) {
+                        flags |= AutofillManager.SET_STATE_FLAG_DEBUG;
+                    }
+                    if (sVerbose) {
+                        flags |= AutofillManager.SET_STATE_FLAG_VERBOSE;
+                    }
+                    client.setState(flags);
                 } catch (RemoteException re) {
                     /* ignore */
                 }
@@ -919,6 +951,13 @@
         return false;
     }
 
+    // TODO(b/67867469): remove once feature is finished
+    boolean isFieldDetectionEnabled() {
+        return Settings.Secure.getIntForUser(
+                mContext.getContentResolver(), Settings.Secure.AUTOFILL_FEATURE_FIELD_DETECTION, 0,
+                mUserId) == 1;
+    }
+
     @Override
     public String toString() {
         return "AutofillManagerServiceImpl: [userId=" + mUserId
diff --git a/com/android/server/autofill/Helper.java b/com/android/server/autofill/Helper.java
index 236fbfd..02a62e1 100644
--- a/com/android/server/autofill/Helper.java
+++ b/com/android/server/autofill/Helper.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
@@ -112,4 +113,12 @@
         }
         return log;
     }
+
+    public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable String text) {
+        if (text == null) {
+            pw.println("null");
+        } else {
+            pw.print(text.length()); pw.println("_chars");
+        }
+    }
 }
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index 010995f..af4668a 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -16,10 +16,10 @@
 
 package com.android.server.autofill;
 
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
-import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
-import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
@@ -36,6 +36,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.IAssistDataReceiver;
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.AutofillOverlay;
 import android.app.assist.AssistStructure.ViewNode;
@@ -43,6 +44,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
 import android.os.Binder;
@@ -53,6 +55,7 @@
 import android.os.SystemClock;
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
+import android.service.autofill.FieldsDetection;
 import android.service.autofill.FillContext;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
@@ -78,7 +81,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
@@ -203,16 +205,16 @@
     /**
      * Receiver of assist data from the app's {@link Activity}.
      */
-    private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
+    private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
         @Override
-        public void send(int resultCode, Bundle resultData) throws RemoteException {
-            final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
+        public void onHandleAssistData(Bundle resultData) throws RemoteException {
+            final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE);
             if (structure == null) {
                 Slog.e(TAG, "No assist structure - app might have crashed providing it");
                 return;
             }
 
-            final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS);
+            final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS);
             if (receiverExtras == null) {
                 Slog.e(TAG, "No receiver extras - app might have crashed providing it");
                 return;
@@ -261,6 +263,11 @@
 
             mRemoteFillService.onFillRequest(request);
         }
+
+        @Override
+        public void onHandleAssistScreenshot(Bitmap screenshot) {
+            // Do nothing
+        }
     };
 
     /**
@@ -486,6 +493,13 @@
             }
         }
 
+        // TODO(b/67867469): remove once feature is finished
+        if (response.getFieldsDetection() != null && !mService.isFieldDetectionEnabled()) {
+            Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
+            processNullResponseLocked(requestFlags);
+            return;
+        }
+
         mService.setLastResponse(serviceUid, id, response);
 
         int sessionFinishedState = 0;
@@ -907,11 +921,29 @@
                 }
             }
         }
-        if (!hasAtLeastOneDataset) {
-            if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets)");
+        final FieldsDetection fieldsDetection = lastResponse.getFieldsDetection();
+
+        if (!hasAtLeastOneDataset && fieldsDetection == null) {
+            if (sVerbose) {
+                Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets nor fields "
+                        + "detection)");
+            }
             return;
         }
 
+        final AutofillId detectableFieldId;
+        final String detectableRemoteId;
+        String detectedRemoteId = null;
+        if (fieldsDetection == null) {
+            detectableFieldId = null;
+            detectableRemoteId = null;
+        } else {
+            detectableFieldId = fieldsDetection.getFieldId();
+            detectableRemoteId = fieldsDetection.getRemoteId();
+        }
+
+        int detectedFieldScore = -1;
+
         for (int i = 0; i < mViewStates.size(); i++) {
             final ViewState viewState = mViewStates.valueAt(i);
             final int state = viewState.getState();
@@ -920,7 +952,6 @@
             // - autofilled -> changedDatasetIds
             // - not autofilled but matches a dataset value -> manuallyFilledIds
             if ((state & ViewState.STATE_CHANGED) != 0) {
-
                 // Check if autofilled value was changed
                 if ((state & ViewState.STATE_AUTOFILLED) != 0) {
                     final String datasetId = viewState.getDatasetId();
@@ -952,7 +983,6 @@
                     changedFieldIds.add(viewState.id);
                     changedDatasetIds.add(datasetId);
                 } else {
-                    // Check if value match a dataset.
                     final AutofillValue currentValue = viewState.getCurrentValue();
                     if (currentValue == null) {
                         if (sDebug) {
@@ -961,58 +991,78 @@
                         }
                         continue;
                     }
-                    for (int j = 0; j < responseCount; j++) {
-                        final FillResponse response = mResponses.valueAt(j);
-                        final List<Dataset> datasets = response.getDatasets();
-                        if (datasets == null || datasets.isEmpty()) {
-                            if (sVerbose) Slog.v(TAG,  "logContextCommitted() no datasets at " + j);
-                        } else {
-                            for (int k = 0; k < datasets.size(); k++) {
-                                final Dataset dataset = datasets.get(k);
-                                final String datasetId = dataset.getId();
-                                if (datasetId == null) {
-                                    if (sVerbose) {
-                                        Slog.v(TAG, "logContextCommitted() skipping idless dataset "
-                                                + dataset);
-                                    }
-                                } else {
-                                    final ArrayList<AutofillValue> values = dataset.getFieldValues();
-                                    for (int l = 0; l < values.size(); l++) {
-                                        final AutofillValue candidate = values.get(l);
-                                        if (currentValue.equals(candidate)) {
-                                            if (sDebug) {
-                                                Slog.d(TAG, "field " + viewState.id
-                                                        + " was manually filled with value set by "
-                                                        + "dataset " + datasetId);
-                                            }
-                                            if (manuallyFilledIds == null) {
-                                                manuallyFilledIds = new ArrayMap<>();
-                                            }
-                                            ArraySet<String> datasetIds =
-                                                    manuallyFilledIds.get(viewState.id);
-                                            if (datasetIds == null) {
-                                                datasetIds = new ArraySet<>(1);
-                                                manuallyFilledIds.put(viewState.id, datasetIds);
-                                            }
-                                            datasetIds.add(datasetId);
-                                        }
-                                    }
-                                    if (mSelectedDatasetIds == null
-                                            || !mSelectedDatasetIds.contains(datasetId)) {
-                                        if (sVerbose) {
-                                            Slog.v(TAG, "adding ignored dataset " + datasetId);
-                                        }
-                                        if (ignoredDatasets == null) {
-                                            ignoredDatasets = new ArraySet<>();
-                                        }
-                                        ignoredDatasets.add(datasetId);
-                                    }
+                    // Check if value match a dataset.
+                    if (hasAtLeastOneDataset) {
+                        for (int j = 0; j < responseCount; j++) {
+                            final FillResponse response = mResponses.valueAt(j);
+                            final List<Dataset> datasets = response.getDatasets();
+                            if (datasets == null || datasets.isEmpty()) {
+                                if (sVerbose) {
+                                    Slog.v(TAG,  "logContextCommitted() no datasets at " + j);
                                 }
-                            }
-                        }
+                            } else {
+                                for (int k = 0; k < datasets.size(); k++) {
+                                    final Dataset dataset = datasets.get(k);
+                                    final String datasetId = dataset.getId();
+                                    if (datasetId == null) {
+                                        if (sVerbose) {
+                                            Slog.v(TAG, "logContextCommitted() skipping idless "
+                                                    + "dataset " + dataset);
+                                        }
+                                    } else {
+                                        final ArrayList<AutofillValue> values =
+                                                dataset.getFieldValues();
+                                        for (int l = 0; l < values.size(); l++) {
+                                            final AutofillValue candidate = values.get(l);
+                                            if (currentValue.equals(candidate)) {
+                                                if (sDebug) {
+                                                    Slog.d(TAG, "field " + viewState.id + " was "
+                                                            + "manually filled with value set by "
+                                                            + "dataset " + datasetId);
+                                                }
+                                                if (manuallyFilledIds == null) {
+                                                    manuallyFilledIds = new ArrayMap<>();
+                                                }
+                                                ArraySet<String> datasetIds =
+                                                        manuallyFilledIds.get(viewState.id);
+                                                if (datasetIds == null) {
+                                                    datasetIds = new ArraySet<>(1);
+                                                    manuallyFilledIds.put(viewState.id, datasetIds);
+                                                }
+                                                datasetIds.add(datasetId);
+                                            }
+                                        } // for l
+                                        if (mSelectedDatasetIds == null
+                                                || !mSelectedDatasetIds.contains(datasetId)) {
+                                            if (sVerbose) {
+                                                Slog.v(TAG, "adding ignored dataset " + datasetId);
+                                            }
+                                            if (ignoredDatasets == null) {
+                                                ignoredDatasets = new ArraySet<>();
+                                            }
+                                            ignoredDatasets.add(datasetId);
+                                        } // if
+                                    } // if
+                                } // for k
+                            } // else
+                        } // for j
                     }
-                }
-            }
+
+                    // Check if detectable field changed.
+                    if (detectableFieldId != null && detectableFieldId.equals(viewState.id)
+                            && currentValue.isText() && currentValue.getTextValue() != null) {
+                        final String actualValue = currentValue.getTextValue().toString();
+                        final String expectedValue = fieldsDetection.getValue();
+                        if (actualValue.equalsIgnoreCase(expectedValue)) {
+                            detectedRemoteId = detectableRemoteId;
+                            detectedFieldScore = 0;
+                        } else if (sVerbose) {
+                            Slog.v(TAG, "Detection mismatch for field " + detectableFieldId);
+                        }
+                        // TODO(b/67867469): set score on partial hits
+                    }
+                } // else
+            } // else
         }
 
         if (sVerbose) {
@@ -1021,7 +1071,10 @@
                     + ", ignoredDatasetIds=" + ignoredDatasets
                     + ", changedAutofillIds=" + changedFieldIds
                     + ", changedDatasetIds=" + changedDatasetIds
-                    + ", manuallyFilledIds=" + manuallyFilledIds);
+                    + ", manuallyFilledIds=" + manuallyFilledIds
+                    + ", detectableFieldId=" + detectableFieldId
+                    + ", detectedFieldScore=" + detectedFieldScore
+                    );
         }
 
         ArrayList<AutofillId> manuallyFilledFieldIds = null;
@@ -1039,9 +1092,11 @@
                 manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds));
             }
         }
+
         mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
                 changedFieldIds, changedDatasetIds,
-                manuallyFilledFieldIds, manuallyFilledDatasetIds);
+                manuallyFilledFieldIds, manuallyFilledDatasetIds,
+                detectedRemoteId, detectedFieldScore);
     }
 
     /**
@@ -1529,6 +1584,10 @@
                 viewState = new ViewState(this, id, this,
                         isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL);
                 mViewStates.put(id, viewState);
+
+                // TODO(b/67867469): for optimization purposes, should also ignore if change is
+                // detectable, and batch-send them when the session is finished (but that will
+                // require tracking detectable fields on AutofillManager)
                 if (isIgnored) {
                     if (sDebug) Slog.d(TAG, "updateLocked(): ignoring view " + id);
                     return;
diff --git a/com/android/server/autofill/ViewState.java b/com/android/server/autofill/ViewState.java
index 1d8110f..832a66b 100644
--- a/com/android/server/autofill/ViewState.java
+++ b/com/android/server/autofill/ViewState.java
@@ -134,7 +134,11 @@
     }
 
     String getStateAsString() {
-        return DebugUtils.flagsToString(ViewState.class, "STATE_", mState);
+        return getStateAsString(mState);
+    }
+
+    static String getStateAsString(int state) {
+        return DebugUtils.flagsToString(ViewState.class, "STATE_", state);
     }
 
     void setState(int state) {
diff --git a/com/android/server/autofill/ui/FillUi.java b/com/android/server/autofill/ui/FillUi.java
index 6d3d792..dac4586 100644
--- a/com/android/server/autofill/ui/FillUi.java
+++ b/com/android/server/autofill/ui/FillUi.java
@@ -49,6 +49,8 @@
 
 import com.android.internal.R;
 import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
+
 import libcore.util.Objects;
 
 import java.io.PrintWriter;
@@ -466,7 +468,8 @@
         pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null);
         pw.print(prefix); pw.print("mListView: "); pw.println(mListView);
         pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null);
-        pw.print(prefix); pw.print("mFilterText: "); pw.println(mFilterText);
+        pw.print(prefix); pw.print("mFilterText: ");
+        Helper.printlnRedactedText(pw, mFilterText);
         pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth);
         pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight);
         pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed);
diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java
index 622b842..e92a564 100644
--- a/com/android/server/backup/BackupManagerService.java
+++ b/com/android/server/backup/BackupManagerService.java
@@ -80,6 +80,7 @@
 import android.content.pm.Signature;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerSaveState;
 import android.os.Binder;
 import android.os.Build;
@@ -126,7 +127,6 @@
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.backup.PackageManagerBackupAgent.Metadata;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
 
 import libcore.io.IoUtils;
 
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index 20f2369..a45a4f0 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -71,6 +71,7 @@
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerSaveState;
 import android.os.Process;
 import android.os.RemoteException;
@@ -120,7 +121,6 @@
 import com.android.server.backup.utils.BackupManagerMonitorUtils;
 import com.android.server.backup.utils.BackupObserverUtils;
 import com.android.server.backup.utils.SparseArrayUtils;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
 
 import com.google.android.collect.Sets;
 
diff --git a/com/android/server/backup/restore/ActiveRestoreSession.java b/com/android/server/backup/restore/ActiveRestoreSession.java
index 8d8a013..a08c19e 100644
--- a/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -158,16 +158,19 @@
                             MSG_RESTORE_SESSION_TIMEOUT);
 
                     long oldId = Binder.clearCallingIdentity();
-                    backupManagerService.getWakelock().acquire();
-                    if (MORE_DEBUG) {
-                        Slog.d(TAG, "restoreAll() kicking off");
+                    try {
+                        backupManagerService.getWakelock().acquire();
+                        if (MORE_DEBUG) {
+                            Slog.d(TAG, "restoreAll() kicking off");
+                        }
+                        Message msg = backupManagerService.getBackupHandler().obtainMessage(
+                                MSG_RUN_RESTORE);
+                        msg.obj = new RestoreParams(mRestoreTransport, dirName,
+                                observer, monitor, token);
+                        backupManagerService.getBackupHandler().sendMessage(msg);
+                    } finally {
+                        Binder.restoreCallingIdentity(oldId);
                     }
-                    Message msg = backupManagerService.getBackupHandler().obtainMessage(
-                            MSG_RUN_RESTORE);
-                    msg.obj = new RestoreParams(mRestoreTransport, dirName,
-                            observer, monitor, token);
-                    backupManagerService.getBackupHandler().sendMessage(msg);
-                    Binder.restoreCallingIdentity(oldId);
                     return 0;
                 }
             }
diff --git a/com/android/server/connectivity/DefaultNetworkMetrics.java b/com/android/server/connectivity/DefaultNetworkMetrics.java
index 8981db1..28c3585 100644
--- a/com/android/server/connectivity/DefaultNetworkMetrics.java
+++ b/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -18,9 +18,11 @@
 
 import android.net.LinkProperties;
 import android.net.metrics.DefaultNetworkEvent;
-import android.net.metrics.IpConnectivityLog;
+import android.os.SystemClock;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.RingBuffer;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 
 import java.io.PrintWriter;
@@ -35,19 +37,49 @@
 
     private static final int ROLLING_LOG_SIZE = 64;
 
+    public final long creationTimeMs = SystemClock.elapsedRealtime();
+
     // Event buffer used for metrics upload. The buffer is cleared when events are collected.
     @GuardedBy("this")
     private final List<DefaultNetworkEvent> mEvents = new ArrayList<>();
 
+    // Rolling event buffer used for dumpsys and bugreports.
+    @GuardedBy("this")
+    private final RingBuffer<DefaultNetworkEvent> mEventsLog =
+            new RingBuffer(DefaultNetworkEvent.class, ROLLING_LOG_SIZE);
+
+    // Information about the current status of the default network.
+    @GuardedBy("this")
+    private DefaultNetworkEvent mCurrentDefaultNetwork;
+    @GuardedBy("this")
+    private boolean mIsCurrentlyValid;
+    @GuardedBy("this")
+    private long mLastValidationTimeMs;
+    // Transport information about the last default network.
+    @GuardedBy("this")
+    private int mLastTransports;
+
+    public DefaultNetworkMetrics() {
+        newDefaultNetwork(creationTimeMs, null);
+    }
+
     public synchronized void listEvents(PrintWriter pw) {
+        pw.println("default network events:");
         long localTimeMs = System.currentTimeMillis();
-        for (DefaultNetworkEvent ev : mEvents) {
-            pw.println(ev);
+        long timeMs = SystemClock.elapsedRealtime();
+        for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
+            printEvent(localTimeMs, pw, ev);
         }
+        mCurrentDefaultNetwork.updateDuration(timeMs);
+        if (mIsCurrentlyValid) {
+            updateValidationTime(timeMs);
+            mLastValidationTimeMs = timeMs;
+        }
+        printEvent(localTimeMs, pw, mCurrentDefaultNetwork);
     }
 
     public synchronized void listEventsAsProto(PrintWriter pw) {
-        for (DefaultNetworkEvent ev : mEvents) {
+        for (DefaultNetworkEvent ev : mEventsLog.toArray()) {
             pw.print(IpConnectivityEventBuilder.toProto(ev));
         }
     }
@@ -59,20 +91,75 @@
         mEvents.clear();
     }
 
-    public synchronized void logDefaultNetworkEvent(
-            NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
-        DefaultNetworkEvent ev = new DefaultNetworkEvent();
-        if (newNai != null) {
-            ev.netId = newNai.network().netId;
-            ev.transportTypes = newNai.networkCapabilities.getTransportTypes();
-        }
-        if (prevNai != null) {
-            ev.prevNetId = prevNai.network().netId;
-            final LinkProperties lp = prevNai.linkProperties;
-            ev.prevIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
-            ev.prevIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
+    public synchronized void logDefaultNetworkValidity(long timeMs, boolean isValid) {
+        if (!isValid && mIsCurrentlyValid) {
+            mIsCurrentlyValid = false;
+            updateValidationTime(timeMs);
         }
 
+        if (isValid && !mIsCurrentlyValid) {
+            mIsCurrentlyValid = true;
+            mLastValidationTimeMs = timeMs;
+        }
+    }
+
+    private void updateValidationTime(long timeMs) {
+        mCurrentDefaultNetwork.validatedMs += timeMs - mLastValidationTimeMs;
+    }
+
+    public synchronized void logDefaultNetworkEvent(
+            long timeMs, NetworkAgentInfo newNai, NetworkAgentInfo oldNai) {
+        logCurrentDefaultNetwork(timeMs, oldNai);
+        newDefaultNetwork(timeMs, newNai);
+    }
+
+    private void logCurrentDefaultNetwork(long timeMs, NetworkAgentInfo oldNai) {
+        DefaultNetworkEvent ev = mCurrentDefaultNetwork;
+        ev.updateDuration(timeMs);
+        ev.previousTransports = mLastTransports;
+        // oldNai is null if the system had no default network before the transition.
+        if (oldNai != null) {
+            // The system acquired a new default network.
+            fillLinkInfo(ev, oldNai);
+            ev.finalScore = oldNai.getCurrentScore();
+            ev.validatedMs = ev.durationMs;
+        }
+        // Only change transport of the previous default network if the event currently logged
+        // corresponds to an existing default network, and not to the absence of a default network.
+        // This allows to log pairs of transports for successive default networks regardless of
+        // whether or not the system experienced a period without any default network.
+        if (ev.transports != 0) {
+            mLastTransports = ev.transports;
+        }
         mEvents.add(ev);
+        mEventsLog.append(ev);
+    }
+
+    private void newDefaultNetwork(long timeMs, NetworkAgentInfo newNai) {
+        DefaultNetworkEvent ev = new DefaultNetworkEvent(timeMs);
+        ev.durationMs = timeMs;
+        // newNai is null if the system has no default network after the transition.
+        if (newNai != null) {
+            fillLinkInfo(ev, newNai);
+            ev.initialScore = newNai.getCurrentScore();
+            if (newNai.lastValidated) {
+                mIsCurrentlyValid = true;
+                mLastValidationTimeMs = timeMs;
+            }
+        }
+        mCurrentDefaultNetwork = ev;
+    }
+
+    private static void fillLinkInfo(DefaultNetworkEvent ev, NetworkAgentInfo nai) {
+        LinkProperties lp = nai.linkProperties;
+        ev.netId = nai.network().netId;
+        ev.transports |= BitUtils.packBits(nai.networkCapabilities.getTransportTypes());
+        ev.ipv4 |= lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
+        ev.ipv6 |= lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
+    }
+
+    private static void printEvent(long localTimeMs, PrintWriter pw, DefaultNetworkEvent ev) {
+        long localCreationTimeMs = localTimeMs - ev.durationMs;
+        pw.println(String.format("%tT.%tL: %s", localCreationTimeMs, localCreationTimeMs, ev));
     }
 }
diff --git a/com/android/server/connectivity/IpConnectivityEventBuilder.java b/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 3d71ecb..397af7b 100644
--- a/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -25,6 +25,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 
+import android.net.ConnectivityManager;
 import android.net.ConnectivityMetricsEvent;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
@@ -45,7 +46,6 @@
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
-import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.NetworkId;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.Pair;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -127,6 +127,11 @@
         wakeupStats.nonApplicationWakeups = in.nonApplicationWakeups;
         wakeupStats.applicationWakeups = in.applicationWakeups;
         wakeupStats.noUidWakeups = in.noUidWakeups;
+        wakeupStats.l2UnicastCount = in.l2UnicastCount;
+        wakeupStats.l2MulticastCount = in.l2MulticastCount;
+        wakeupStats.l2BroadcastCount = in.l2BroadcastCount;
+        wakeupStats.ethertypeCounts = toPairArray(in.ethertypes);
+        wakeupStats.ipNextHeaderCounts = toPairArray(in.ipNextHeaders);
         final IpConnectivityEvent out = buildEvent(0, 0, in.iface);
         out.setWakeupStats(wakeupStats);
         return out;
@@ -135,11 +140,17 @@
     public static IpConnectivityEvent toProto(DefaultNetworkEvent in) {
         IpConnectivityLogClass.DefaultNetworkEvent ev =
                 new IpConnectivityLogClass.DefaultNetworkEvent();
-        ev.networkId = netIdOf(in.netId);
-        ev.previousNetworkId = netIdOf(in.prevNetId);
-        ev.transportTypes = in.transportTypes;
-        ev.previousNetworkIpSupport = ipSupportOf(in);
-        final IpConnectivityEvent out = buildEvent(in.netId, 0, null);
+        ev.finalScore = in.finalScore;
+        ev.initialScore = in.initialScore;
+        ev.ipSupport = ipSupportOf(in);
+        ev.defaultNetworkDurationMs = in.durationMs;
+        ev.validationDurationMs = in.validatedMs;
+        ev.previousDefaultNetworkLinkLayer = transportsToLinkLayer(in.previousTransports);
+        final IpConnectivityEvent out = buildEvent(in.netId, in.transports, null);
+        if (in.transports == 0) {
+            // Set link layer to NONE for events representing the absence of a default network.
+            out.linkLayer = IpConnectivityLogClass.NONE;
+        }
         out.setDefaultNetworkEvent(ev);
         return out;
     }
@@ -235,7 +246,6 @@
     private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) {
         IpConnectivityLogClass.NetworkEvent networkEvent =
                 new IpConnectivityLogClass.NetworkEvent();
-        networkEvent.networkId = netIdOf(in.netId);
         networkEvent.eventType = in.eventType;
         networkEvent.latencyMs = (int) in.durationMs;
         out.setNetworkEvent(networkEvent);
@@ -314,20 +324,14 @@
         return pairs;
     }
 
-    private static NetworkId netIdOf(int netid) {
-        final NetworkId ni = new NetworkId();
-        ni.networkId = netid;
-        return ni;
-    }
-
     private static int ipSupportOf(DefaultNetworkEvent in) {
-        if (in.prevIPv4 && in.prevIPv6) {
+        if (in.ipv4 && in.ipv6) {
             return IpConnectivityLogClass.DefaultNetworkEvent.DUAL;
         }
-        if (in.prevIPv6) {
+        if (in.ipv6) {
             return IpConnectivityLogClass.DefaultNetworkEvent.IPV6;
         }
-        if (in.prevIPv4) {
+        if (in.ipv4) {
             return IpConnectivityLogClass.DefaultNetworkEvent.IPV4;
         }
         return IpConnectivityLogClass.DefaultNetworkEvent.NONE;
diff --git a/com/android/server/connectivity/IpConnectivityMetrics.java b/com/android/server/connectivity/IpConnectivityMetrics.java
index 24217e6..f427819 100644
--- a/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -23,8 +23,6 @@
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.os.Binder;
-import android.os.IBinder;
-import android.os.Parcelable;
 import android.os.Process;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -45,6 +43,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.ToIntFunction;
 
@@ -214,86 +213,66 @@
     }
 
     /**
-     * Clears the event buffer and prints its content as a protobuf serialized byte array
+     * Clear the event buffer and prints its content as a protobuf serialized byte array
      * inside a base64 encoded string.
      */
-    private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void cmdFlush(PrintWriter pw) {
         pw.print(flushEncodedOutput());
     }
 
     /**
-     * Prints the content of the event buffer, either using the events ASCII representation
-     * or using protobuf text format.
+     * Print the content of the rolling event buffer in human readable format.
+     * Also print network dns/connect statistics and recent default network events.
      */
-    private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) {
-        final ArrayList<ConnectivityMetricsEvent> events;
-        synchronized (mLock) {
-            events = new ArrayList(mBuffer);
-        }
-
-        if (args.length > 1 && args[1].equals("proto")) {
-            for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
-                pw.print(ev.toString());
-            }
-            if (mNetdListener != null) {
-                mNetdListener.listAsProtos(pw);
-            }
-            mDefaultNetworkMetrics.listEventsAsProto(pw);
-            return;
-        }
-
+    private void cmdList(PrintWriter pw) {
+        pw.println("metrics events:");
+        final List<ConnectivityMetricsEvent> events = getEvents();
         for (ConnectivityMetricsEvent ev : events) {
             pw.println(ev.toString());
         }
+        pw.println("");
         if (mNetdListener != null) {
             mNetdListener.list(pw);
         }
+        pw.println("");
         mDefaultNetworkMetrics.listEvents(pw);
     }
 
-    /**
-     * Prints for bug reports the content of the rolling event log and the
-     * content of Netd event listener.
+    /*
+     * Print the content of the rolling event buffer in text proto format.
      */
-    private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
-        final ConnectivityMetricsEvent[] events;
-        synchronized (mLock) {
-            events = mEventLog.toArray();
-        }
-        for (ConnectivityMetricsEvent ev : events) {
-            pw.println(ev.toString());
+    private void cmdListAsProto(PrintWriter pw) {
+        final List<ConnectivityMetricsEvent> events = getEvents();
+        for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) {
+            pw.print(ev.toString());
         }
         if (mNetdListener != null) {
-            mNetdListener.list(pw);
+            mNetdListener.listAsProtos(pw);
         }
-        mDefaultNetworkMetrics.listEvents(pw);
+        mDefaultNetworkMetrics.listEventsAsProto(pw);
     }
 
-    private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
+    /*
+     * Return a copy of metrics events stored in buffer for metrics uploading.
+     */
+    private List<ConnectivityMetricsEvent> getEvents() {
         synchronized (mLock) {
-            pw.println("Buffered events: " + mBuffer.size());
-            pw.println("Buffer capacity: " + mCapacity);
-            pw.println("Dropped events: " + mDropped);
+            return Arrays.asList(mEventLog.toArray());
         }
-        if (mNetdListener != null) {
-            mNetdListener.dump(pw);
-        }
-    }
-
-    private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (args.length == 0) {
-            pw.println("No command");
-            return;
-        }
-        pw.println("Unknown command " + TextUtils.join(" ", args));
     }
 
     public final class Impl extends IIpConnectivityMetrics.Stub {
-        static final String CMD_FLUSH   = "flush";
-        static final String CMD_LIST    = "list";
-        static final String CMD_STATS   = "stats";
-        static final String CMD_DUMPSYS = "-a"; // dumpsys.cpp dumps services with "-a" as arguments
-        static final String CMD_DEFAULT = CMD_STATS;
+        // Dump and flushes the metrics event buffer in base64 encoded serialized proto output.
+        static final String CMD_FLUSH = "flush";
+        // Dump the rolling buffer of metrics event in human readable proto text format.
+        static final String CMD_PROTO = "proto";
+        // Dump the rolling buffer of metrics event and pretty print events using a human readable
+        // format. Also print network dns/connect statistics and default network event time series.
+        static final String CMD_LIST = "list";
+        // By default any other argument will fall into the default case which is remapped to the
+        // "list" command. This includes most notably bug reports collected by dumpsys.cpp with
+        // the "-a" argument.
+        static final String CMD_DEFAULT = CMD_LIST;
 
         @Override
         public int logEvent(ConnectivityMetricsEvent event) {
@@ -308,19 +287,15 @@
             final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT;
             switch (cmd) {
                 case CMD_FLUSH:
-                    cmdFlush(fd, pw, args);
+                    cmdFlush(pw);
                     return;
-                case CMD_DUMPSYS:
-                    cmdDumpsys(fd, pw, args);
+                case CMD_PROTO:
+                    cmdListAsProto(pw);
                     return;
-                case CMD_LIST:
-                    cmdList(fd, pw, args);
-                    return;
-                case CMD_STATS:
-                    cmdStats(fd, pw, args);
-                    return;
+                case CMD_LIST: // fallthrough
                 default:
-                    cmdDefault(fd, pw, args);
+                    cmdList(pw);
+                    return;
             }
         }
 
@@ -345,22 +320,22 @@
         }
 
         @Override
-        public boolean registerNetdEventCallback(INetdEventCallback callback) {
+        public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
             enforceNetdEventListeningPermission();
             if (mNetdListener == null) {
                 return false;
             }
-            return mNetdListener.registerNetdEventCallback(callback);
+            return mNetdListener.addNetdEventCallback(callerType, callback);
         }
 
         @Override
-        public boolean unregisterNetdEventCallback() {
+        public boolean removeNetdEventCallback(int callerType) {
             enforceNetdEventListeningPermission();
             if (mNetdListener == null) {
                 // if the service is null, we aren't registered anyway
                 return true;
             }
-            return mNetdListener.unregisterNetdEventCallback();
+            return mNetdListener.removeNetdEventCallback(callerType);
         }
     };
 
diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java
index 05c6e69..4bdbbe3 100644
--- a/com/android/server/connectivity/NetdEventListenerService.java
+++ b/com/android/server/connectivity/NetdEventListenerService.java
@@ -58,7 +58,6 @@
 
     private static final String TAG = NetdEventListenerService.class.getSimpleName();
     private static final boolean DBG = false;
-    private static final boolean VDBG = false;
 
     // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
     // bursts of 5000 measurements.
@@ -98,21 +97,55 @@
     @GuardedBy("this")
     private final TokenBucket mConnectTb =
             new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
-    // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM
-    // by the device owner. It's DevicePolicyManager's responsibility to ensure that.
-    @GuardedBy("this")
-    private INetdEventCallback mNetdEventCallback;
 
-    public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) {
-        mNetdEventCallback = callback;
+
+    /**
+     * There are only 2 possible callbacks.
+     *
+     * mNetdEventCallbackList[CALLBACK_CALLER_DEVICE_POLICY].
+     * Callback registered/unregistered when logging is being enabled/disabled in DPM
+     * by the device owner. It's DevicePolicyManager's responsibility to ensure that.
+     *
+     * mNetdEventCallbackList[CALLBACK_CALLER_NETWORK_WATCHLIST]
+     * Callback registered/unregistered by NetworkWatchlistService.
+     */
+    @GuardedBy("this")
+    private static final int[] ALLOWED_CALLBACK_TYPES = {
+        INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY,
+        INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST
+    };
+
+    @GuardedBy("this")
+    private INetdEventCallback[] mNetdEventCallbackList =
+            new INetdEventCallback[ALLOWED_CALLBACK_TYPES.length];
+
+    public synchronized boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
+        if (!isValidCallerType(callerType)) {
+            Log.e(TAG, "Invalid caller type: " + callerType);
+            return false;
+        }
+        mNetdEventCallbackList[callerType] = callback;
         return true;
     }
 
-    public synchronized boolean unregisterNetdEventCallback() {
-        mNetdEventCallback = null;
+    public synchronized boolean removeNetdEventCallback(int callerType) {
+        if (!isValidCallerType(callerType)) {
+            Log.e(TAG, "Invalid caller type: " + callerType);
+            return false;
+        }
+        mNetdEventCallbackList[callerType] = null;
         return true;
     }
 
+    private static boolean isValidCallerType(int callerType) {
+        for (int i = 0; i < ALLOWED_CALLBACK_TYPES.length; i++) {
+            if (callerType == ALLOWED_CALLBACK_TYPES[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public NetdEventListenerService(Context context) {
         this(context.getSystemService(ConnectivityManager.class));
     }
@@ -164,13 +197,13 @@
     public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
             String hostname, String[] ipAddresses, int ipAddressesCount, int uid)
             throws RemoteException {
-        maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs);
-
         long timestamp = System.currentTimeMillis();
         getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
 
-        if (mNetdEventCallback != null) {
-            mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
+        for (INetdEventCallback callback : mNetdEventCallbackList) {
+            if (callback != null) {
+                callback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
+            }
         }
     }
 
@@ -179,22 +212,23 @@
     // This method must not block or perform long-running operations.
     public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr,
             int port, int uid) throws RemoteException {
-        maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs);
-
         long timestamp = System.currentTimeMillis();
         getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
 
-        if (mNetdEventCallback != null) {
-            mNetdEventCallback.onConnectEvent(ipAddr, port, timestamp, uid);
+        for (INetdEventCallback callback : mNetdEventCallbackList) {
+            if (callback != null) {
+                // TODO(rickywai): Remove this checking to collect ip in watchlist.
+                if (callback ==
+                        mNetdEventCallbackList[INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY]) {
+                    callback.onConnectEvent(ipAddr, port, timestamp, uid);
+                }
+            }
         }
     }
 
     @Override
-    public synchronized void onWakeupEvent(String prefix, int uid, int gid, long timestampNs) {
-        maybeVerboseLog("onWakeupEvent(%s, %d, %d, %sns)", prefix, uid, gid, timestampNs);
-
-        // TODO: add ip protocol and port
-
+    public synchronized void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
+            byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort, long timestampNs) {
         String iface = prefix.replaceFirst(WAKEUP_EVENT_IFACE_PREFIX, "");
         final long timestampMs;
         if (timestampNs > 0) {
@@ -203,15 +237,22 @@
             timestampMs = System.currentTimeMillis();
         }
 
-        addWakeupEvent(iface, timestampMs, uid);
-    }
-
-    @GuardedBy("this")
-    private void addWakeupEvent(String iface, long timestampMs, int uid) {
         WakeupEvent event = new WakeupEvent();
         event.iface = iface;
         event.timestampMs = timestampMs;
         event.uid = uid;
+        event.ethertype = ethertype;
+        event.dstHwAddr = dstHw;
+        event.srcIp = srcIp;
+        event.dstIp = dstIp;
+        event.ipNextHeader = ipNextHeader;
+        event.srcPort = srcPort;
+        event.dstPort = dstPort;
+        addWakeupEvent(event);
+    }
+
+    private void addWakeupEvent(WakeupEvent event) {
+        String iface = event.iface;
         mWakeupEvents.append(event);
         WakeupStats stats = mWakeupStats.get(iface);
         if (stats == null) {
@@ -243,24 +284,21 @@
         mWakeupStats.clear();
     }
 
-    public synchronized void dump(PrintWriter writer) {
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println(TAG + ":");
-        pw.increaseIndent();
-        list(pw);
-        pw.decreaseIndent();
-    }
-
     public synchronized void list(PrintWriter pw) {
+        pw.println("dns/connect events:");
         for (int i = 0; i < mNetworkMetrics.size(); i++) {
             pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
         }
         for (int i = 0; i < mNetworkMetrics.size(); i++) {
             pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
         }
+        pw.println("");
+        pw.println("network statistics:");
         for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
             pw.println(s);
         }
+        pw.println("");
+        pw.println("packet wakeup events:");
         for (int i = 0; i < mWakeupStats.size(); i++) {
             pw.println(mWakeupStats.valueAt(i));
         }
@@ -294,10 +332,6 @@
         if (DBG) Log.d(TAG, String.format(s, args));
     }
 
-    private static void maybeVerboseLog(String s, Object... args) {
-        if (VDBG) Log.d(TAG, String.format(s, args));
-    }
-
     /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
     static class NetworkMetricsSnapshot {
 
diff --git a/com/android/server/connectivity/NetworkMonitor.java b/com/android/server/connectivity/NetworkMonitor.java
index 8b886d6..7684030 100644
--- a/com/android/server/connectivity/NetworkMonitor.java
+++ b/com/android/server/connectivity/NetworkMonitor.java
@@ -1129,7 +1129,8 @@
     }
 
     private void logNetworkEvent(int evtype) {
-        mMetricsLog.log(new NetworkEvent(mNetId, evtype));
+        int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+        mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype));
     }
 
     private int networkEventType(ValidationStage s, EvaluationResult r) {
@@ -1150,7 +1151,8 @@
 
     private void maybeLogEvaluationResult(int evtype) {
         if (mEvaluationTimer.isRunning()) {
-            mMetricsLog.log(new NetworkEvent(mNetId, evtype, mEvaluationTimer.stop()));
+            int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+            mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop()));
             mEvaluationTimer.reset();
         }
     }
diff --git a/com/android/server/connectivity/Vpn.java b/com/android/server/connectivity/Vpn.java
index a44b18d..7715727 100644
--- a/com/android/server/connectivity/Vpn.java
+++ b/com/android/server/connectivity/Vpn.java
@@ -18,6 +18,8 @@
 
 import static android.Manifest.permission.BIND_VPN_SERVICE;
 import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 import static android.net.RouteInfo.RTN_THROW;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 
@@ -90,6 +92,8 @@
 import com.android.internal.net.VpnInfo;
 import com.android.internal.net.VpnProfile;
 import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.ConnectivityService;
 import com.android.server.DeviceIdleController;
 import com.android.server.LocalServices;
 import com.android.server.net.BaseNetworkObserver;
@@ -245,10 +249,10 @@
         }
 
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, "");
-        // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332
         mNetworkCapabilities = new NetworkCapabilities();
         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+        updateCapabilities();
 
         loadAlwaysOnPackage();
     }
@@ -275,6 +279,62 @@
         updateAlwaysOnNotification(detailedState);
     }
 
+    public void updateCapabilities() {
+        final Network[] underlyingNetworks = (mConfig != null) ? mConfig.underlyingNetworks : null;
+        updateCapabilities(mContext.getSystemService(ConnectivityManager.class), underlyingNetworks,
+                mNetworkCapabilities);
+
+        if (mNetworkAgent != null) {
+            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+        }
+    }
+
+    @VisibleForTesting
+    public static void updateCapabilities(ConnectivityManager cm, Network[] underlyingNetworks,
+            NetworkCapabilities caps) {
+        int[] transportTypes = new int[] { NetworkCapabilities.TRANSPORT_VPN };
+        int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+        int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+        boolean metered = false;
+        boolean roaming = false;
+
+        if (ArrayUtils.isEmpty(underlyingNetworks)) {
+            // No idea what the underlying networks are; assume sane defaults
+            metered = true;
+            roaming = false;
+        } else {
+            for (Network underlying : underlyingNetworks) {
+                final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying);
+                for (int underlyingType : underlyingCaps.getTransportTypes()) {
+                    transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType);
+                }
+
+                // When we have multiple networks, we have to assume the
+                // worst-case link speed and restrictions.
+                downKbps = NetworkCapabilities.minBandwidth(downKbps,
+                        underlyingCaps.getLinkDownstreamBandwidthKbps());
+                upKbps = NetworkCapabilities.minBandwidth(upKbps,
+                        underlyingCaps.getLinkUpstreamBandwidthKbps());
+                metered |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_METERED);
+                roaming |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+            }
+        }
+
+        caps.setTransportTypes(transportTypes);
+        caps.setLinkDownstreamBandwidthKbps(downKbps);
+        caps.setLinkUpstreamBandwidthKbps(upKbps);
+        if (metered) {
+            caps.removeCapability(NET_CAPABILITY_NOT_METERED);
+        } else {
+            caps.addCapability(NET_CAPABILITY_NOT_METERED);
+        }
+        if (roaming) {
+            caps.removeCapability(NET_CAPABILITY_NOT_ROAMING);
+        } else {
+            caps.addCapability(NET_CAPABILITY_NOT_ROAMING);
+        }
+    }
+
     /**
      * Chooses whether to force all connections to go though VPN.
      *
@@ -1344,6 +1404,7 @@
                 }
             }
         }
+        updateCapabilities();
         return true;
     }
 
diff --git a/com/android/server/content/ContentService.java b/com/android/server/content/ContentService.java
index 6e1c21e..c4e6ff6 100644
--- a/com/android/server/content/ContentService.java
+++ b/com/android/server/content/ContentService.java
@@ -163,10 +163,6 @@
     };
 
     private SyncManager getSyncManager() {
-        if (SystemProperties.getBoolean("config.disable_network", false)) {
-            return null;
-        }
-
         synchronized(mSyncManagerLock) {
             try {
                 // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
diff --git a/com/android/server/coverage/CoverageService.java b/com/android/server/coverage/CoverageService.java
index d600aa8..ed8d24a 100644
--- a/com/android/server/coverage/CoverageService.java
+++ b/com/android/server/coverage/CoverageService.java
@@ -112,7 +112,7 @@
             }
 
             // Try to open the destination file
-            ParcelFileDescriptor fd = openOutputFileForSystem(dest);
+            ParcelFileDescriptor fd = openFileForSystem(dest, "w");
             if (fd == null) {
                 return -1;
             }
diff --git a/com/android/server/devicepolicy/NetworkLogger.java b/com/android/server/devicepolicy/NetworkLogger.java
index 0085931..0aaf32c 100644
--- a/com/android/server/devicepolicy/NetworkLogger.java
+++ b/com/android/server/devicepolicy/NetworkLogger.java
@@ -107,7 +107,8 @@
             return false;
         }
         try {
-           if (mIpConnectivityMetrics.registerNetdEventCallback(mNetdEventCallback)) {
+           if (mIpConnectivityMetrics.addNetdEventCallback(
+                   INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, mNetdEventCallback)) {
                 mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
                         /* allowIo */ false);
                 mHandlerThread.start();
@@ -138,7 +139,8 @@
                 // logging is forcefully disabled even if unregistering fails
                 return true;
             }
-            return mIpConnectivityMetrics.unregisterNetdEventCallback();
+            return mIpConnectivityMetrics.removeNetdEventCallback(
+                    INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY);
         } catch (RemoteException re) {
             Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re);
             return true;
diff --git a/com/android/server/display/BrightnessTracker.java b/com/android/server/display/BrightnessTracker.java
new file mode 100644
index 0000000..361d928
--- /dev/null
+++ b/com/android/server/display/BrightnessTracker.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ParceledListSlice;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.BrightnessChangeEvent;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.RingBuffer;
+
+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.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+import java.util.Deque;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class that tracks recent brightness settings changes and stores
+ * associated information such as light sensor readings.
+ */
+public class BrightnessTracker {
+
+    private static final String TAG = "BrightnessTracker";
+    private static final boolean DEBUG = false;
+
+    private static final String EVENTS_FILE = "brightness_events.xml";
+    private static final int MAX_EVENTS = 100;
+    // Discard events when reading or writing that are older than this.
+    private static final long MAX_EVENT_AGE = TimeUnit.DAYS.toMillis(30);
+    // Time over which we keep lux sensor readings.
+    private static final long LUX_EVENT_HORIZON = TimeUnit.SECONDS.toNanos(10);
+
+    private static final String TAG_EVENTS = "events";
+    private static final String TAG_EVENT = "event";
+    private static final String ATTR_BRIGHTNESS = "brightness";
+    private static final String ATTR_TIMESTAMP = "timestamp";
+    private static final String ATTR_PACKAGE_NAME = "packageName";
+    private static final String ATTR_USER = "user";
+    private static final String ATTR_LUX = "lux";
+    private static final String ATTR_LUX_TIMESTAMPS = "luxTimestamps";
+    private static final String ATTR_BATTERY_LEVEL = "batteryLevel";
+    private static final String ATTR_NIGHT_MODE = "nightMode";
+    private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature";
+    private static final String ATTR_LAST_BRIGHTNESS = "lastBrightness";
+
+    // Lock held while accessing mEvents, is held while writing events to flash.
+    private final Object mEventsLock = new Object();
+    @GuardedBy("mEventsLock")
+    private RingBuffer<BrightnessChangeEvent> mEvents
+            = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+    private final Runnable mEventsWriter = () -> writeEvents();
+    private volatile boolean mWriteEventsScheduled;
+
+    private UserManager mUserManager;
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+    private Handler mBgHandler;
+    // mSettingsObserver, mBroadcastReceiver and mSensorListener should only be used on
+    // the mBgHandler thread.
+    private SettingsObserver mSettingsObserver;
+    private BroadcastReceiver mBroadcastReceiver;
+    private SensorListener mSensorListener;
+
+    // Lock held while collecting data related to brightness changes.
+    private final Object mDataCollectionLock = new Object();
+    @GuardedBy("mDataCollectionLock")
+    private Deque<LightData> mLastSensorReadings = new ArrayDeque<>();
+    @GuardedBy("mDataCollectionLock")
+    private float mLastBatteryLevel = Float.NaN;
+    @GuardedBy("mDataCollectionLock")
+    private int mIgnoreBrightness = -1;
+    @GuardedBy("mDataCollectionLock")
+    private int mLastBrightness = -1;
+
+    private final Injector mInjector;
+
+    public BrightnessTracker(Context context, @Nullable Injector injector) {
+        // Note this will be called very early in boot, other system
+        // services may not be present.
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        if (injector != null) {
+            mInjector = injector;
+        } else {
+            mInjector = new Injector();
+        }
+    }
+
+    /** Start listening for brightness slider events */
+    public void start() {
+        if (DEBUG) {
+            Slog.d(TAG, "Start");
+        }
+        mBgHandler = mInjector.getBackgroundHandler();
+        mUserManager = mContext.getSystemService(UserManager.class);
+
+        mBgHandler.post(() -> backgroundStart());
+    }
+
+    private void backgroundStart() {
+        readEvents();
+
+        mLastBrightness = mInjector.getSystemIntForUser(mContentResolver,
+                Settings.System.SCREEN_BRIGHTNESS, -1,
+                UserHandle.USER_CURRENT);
+
+        mSensorListener = new SensorListener();
+        mInjector.registerSensorListener(mContext, mSensorListener);
+
+        mSettingsObserver = new SettingsObserver(mBgHandler);
+        mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver);
+
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SHUTDOWN);
+        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        mBroadcastReceiver = new Receiver();
+        mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
+    }
+
+    /** Stop listening for events */
+    @VisibleForTesting
+    void stop() {
+        if (DEBUG) {
+            Slog.d(TAG, "Stop");
+        }
+        mInjector.unregisterSensorListener(mContext, mSensorListener);
+        mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
+        mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+    }
+
+    /**
+     * @param userId userId to fetch data for.
+     * @return List of recent {@link BrightnessChangeEvent}s
+     */
+    public ParceledListSlice<BrightnessChangeEvent> getEvents(int userId) {
+        // TODO include apps from any managed profiles in the brightness information.
+        BrightnessChangeEvent[] events;
+        synchronized (mEventsLock) {
+            events = mEvents.toArray();
+        }
+        ArrayList<BrightnessChangeEvent> out = new ArrayList<>(events.length);
+        for (int i = 0; i < events.length; ++i) {
+            if (events[i].userId == userId) {
+                out.add(events[i]);
+            }
+        }
+        return new ParceledListSlice<>(out);
+    }
+
+    /** Sets brightness without logging the brightness change event */
+    public void setBrightness(int brightness, int userId) {
+        synchronized (mDataCollectionLock) {
+            mIgnoreBrightness = brightness;
+        }
+        mInjector.putSystemIntForUser(mContentResolver, Settings.System.SCREEN_BRIGHTNESS,
+                brightness, userId);
+    }
+
+    private void handleBrightnessChanged() {
+        if (DEBUG) {
+            Slog.d(TAG, "Brightness change");
+        }
+        final BrightnessChangeEvent event = new BrightnessChangeEvent();
+        event.timeStamp = mInjector.currentTimeMillis();
+
+        int brightness = mInjector.getSystemIntForUser(mContentResolver,
+                Settings.System.SCREEN_BRIGHTNESS, -1,
+                UserHandle.USER_CURRENT);
+
+        synchronized (mDataCollectionLock) {
+            int previousBrightness = mLastBrightness;
+            mLastBrightness = brightness;
+
+            if (brightness == -1 || brightness == mIgnoreBrightness) {
+                // Notified of brightness change but no setting or self change so ignore.
+                mIgnoreBrightness = -1;
+                return;
+            }
+
+            final int readingCount = mLastSensorReadings.size();
+            if (readingCount == 0) {
+                // No sensor data so ignore this.
+                return;
+            }
+
+            event.luxValues = new float[readingCount];
+            event.luxTimestamps = new long[readingCount];
+
+            int pos = 0;
+
+            // Convert sensor timestamp in elapsed time nanos to current time millis.
+            long currentTimeMillis = mInjector.currentTimeMillis();
+            long elapsedTimeNanos = mInjector.elapsedRealtimeNanos();
+            for (LightData reading : mLastSensorReadings) {
+                event.luxValues[pos] = reading.lux;
+                event.luxTimestamps[pos] = currentTimeMillis -
+                        TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp);
+                ++pos;
+            }
+
+            event.batteryLevel = mLastBatteryLevel;
+            event.lastBrightness = previousBrightness;
+        }
+
+        event.brightness = brightness;
+
+        try {
+            final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack();
+            event.userId = focusedStack.userId;
+            event.packageName = focusedStack.topActivity.getPackageName();
+        } catch (RemoteException e) {
+            // Really shouldn't be possible.
+        }
+
+        event.nightMode = mInjector.getSecureIntForUser(mContentResolver,
+                Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 0, UserHandle.USER_CURRENT)
+                == 1;
+        event.colorTemperature = mInjector.getSecureIntForUser(mContentResolver,
+                Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
+                0, UserHandle.USER_CURRENT);
+
+        if (DEBUG) {
+            Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
+        }
+        synchronized (mEventsLock) {
+            mEvents.append(event);
+        }
+    }
+
+    private void scheduleWriteEvents() {
+        if (!mWriteEventsScheduled) {
+            mBgHandler.post(mEventsWriter);
+            mWriteEventsScheduled = true;
+        }
+    }
+
+    private void writeEvents() {
+        mWriteEventsScheduled = false;
+        // TODO kick off write on handler thread e.g. every 24 hours.
+        synchronized (mEventsLock) {
+            final AtomicFile writeTo = mInjector.getFile();
+            if (writeTo == null) {
+                return;
+            }
+            if (mEvents.isEmpty()) {
+                if (writeTo.exists()) {
+                    writeTo.delete();
+                }
+            } else {
+                FileOutputStream output = null;
+                try {
+                    output = writeTo.startWrite();
+                    writeEventsLocked(output);
+                    writeTo.finishWrite(output);
+                } catch (IOException e) {
+                    writeTo.failWrite(output);
+                    Slog.e(TAG, "Failed to write change mEvents.", e);
+                }
+            }
+        }
+    }
+
+    private void readEvents() {
+        synchronized (mEventsLock) {
+            mEvents.clear();
+            final AtomicFile readFrom = mInjector.getFile();
+            if (readFrom != null && readFrom.exists()) {
+                FileInputStream input = null;
+                try {
+                    input = readFrom.openRead();
+                    readEventsLocked(input);
+                } catch (IOException e) {
+                    readFrom.delete();
+                    Slog.e(TAG, "Failed to read change mEvents.", e);
+                } finally {
+                    IoUtils.closeQuietly(input);
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mEventsLock")
+    void writeEventsLocked(OutputStream stream) throws IOException {
+        XmlSerializer out = new FastXmlSerializer();
+        out.setOutput(stream, StandardCharsets.UTF_8.name());
+        out.startDocument(null, true);
+        out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+        out.startTag(null, TAG_EVENTS);
+        BrightnessChangeEvent[] toWrite = mEvents.toArray();
+        if (DEBUG) {
+            Slog.d(TAG, "Writing events " + toWrite.length);
+        }
+        final long timeCutOff = System.currentTimeMillis() - MAX_EVENT_AGE;
+        for (int i = 0; i < toWrite.length; ++i) {
+            int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId);
+            if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
+                out.startTag(null, TAG_EVENT);
+                out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness));
+                out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
+                out.attribute(null, ATTR_PACKAGE_NAME, toWrite[i].packageName);
+                out.attribute(null, ATTR_USER, Integer.toString(userSerialNo));
+                out.attribute(null, ATTR_BATTERY_LEVEL, Float.toString(toWrite[i].batteryLevel));
+                out.attribute(null, ATTR_NIGHT_MODE, Boolean.toString(toWrite[i].nightMode));
+                out.attribute(null, ATTR_COLOR_TEMPERATURE, Integer.toString(
+                        toWrite[i].colorTemperature));
+                out.attribute(null, ATTR_LAST_BRIGHTNESS,
+                        Integer.toString(toWrite[i].lastBrightness));
+                StringBuilder luxValues = new StringBuilder();
+                StringBuilder luxTimestamps = new StringBuilder();
+                for (int j = 0; j < toWrite[i].luxValues.length; ++j) {
+                    if (j > 0) {
+                        luxValues.append(',');
+                        luxTimestamps.append(',');
+                    }
+                    luxValues.append(Float.toString(toWrite[i].luxValues[j]));
+                    luxTimestamps.append(Long.toString(toWrite[i].luxTimestamps[j]));
+                }
+                out.attribute(null, ATTR_LUX, luxValues.toString());
+                out.attribute(null, ATTR_LUX_TIMESTAMPS, luxTimestamps.toString());
+                out.endTag(null, TAG_EVENT);
+            }
+        }
+        out.endTag(null, TAG_EVENTS);
+        out.endDocument();
+        stream.flush();
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mEventsLock")
+    void readEventsLocked(InputStream stream) throws IOException {
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, StandardCharsets.UTF_8.name());
+
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && type != XmlPullParser.START_TAG) {
+            }
+            String tag = parser.getName();
+            if (!TAG_EVENTS.equals(tag)) {
+                throw new XmlPullParserException(
+                        "Events not found in brightness tracker file " + tag);
+            }
+
+            final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
+
+            parser.next();
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+                tag = parser.getName();
+                if (TAG_EVENT.equals(tag)) {
+                    BrightnessChangeEvent event = new BrightnessChangeEvent();
+
+                    String brightness = parser.getAttributeValue(null, ATTR_BRIGHTNESS);
+                    event.brightness = Integer.parseInt(brightness);
+                    String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP);
+                    event.timeStamp = Long.parseLong(timestamp);
+                    event.packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+                    String user = parser.getAttributeValue(null, ATTR_USER);
+                    event.userId = mInjector.getUserId(mUserManager, Integer.parseInt(user));
+                    String batteryLevel = parser.getAttributeValue(null, ATTR_BATTERY_LEVEL);
+                    event.batteryLevel = Float.parseFloat(batteryLevel);
+                    String nightMode = parser.getAttributeValue(null, ATTR_NIGHT_MODE);
+                    event.nightMode = Boolean.parseBoolean(nightMode);
+                    String colorTemperature =
+                            parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE);
+                    event.colorTemperature = Integer.parseInt(colorTemperature);
+                    String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_BRIGHTNESS);
+                    event.lastBrightness = Integer.parseInt(lastBrightness);
+
+                    String luxValue = parser.getAttributeValue(null, ATTR_LUX);
+                    String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS);
+
+                    String[] luxValues = luxValue.split(",");
+                    String[] luxTimestamps = luxTimestamp.split(",");
+                    if (luxValues.length != luxTimestamps.length) {
+                        continue;
+                    }
+                    event.luxValues = new float[luxValues.length];
+                    event.luxTimestamps = new long[luxValues.length];
+                    for (int i = 0; i < luxValues.length; ++i) {
+                        event.luxValues[i] = Float.parseFloat(luxValues[i]);
+                        event.luxTimestamps[i] = Long.parseLong(luxTimestamps[i]);
+                    }
+
+                    if (DEBUG) {
+                        Slog.i(TAG, "Read event " + event.brightness
+                                + " " + event.packageName);
+                    }
+
+                    if (event.userId != -1 && event.timeStamp > timeCutOff
+                            && event.luxValues.length > 0) {
+                        mEvents.append(event);
+                    }
+                }
+            }
+        } catch (NullPointerException | NumberFormatException | XmlPullParserException
+                | IOException e) {
+            // Failed to parse something, just start with an empty event log.
+            mEvents = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+            Slog.e(TAG, "Failed to parse brightness event", e);
+            // Re-throw so we will delete the bad file.
+            throw new IOException("failed to parse file", e);
+        }
+    }
+
+    // Not allowed to keep the SensorEvent so used to copy the data we care about.
+    private static class LightData {
+        public float lux;
+        // Time in elapsedRealtimeNanos
+        public long timestamp;
+    }
+
+    private void recordSensorEvent(SensorEvent event) {
+        long horizon = mInjector.elapsedRealtimeNanos() - LUX_EVENT_HORIZON;
+        synchronized (mDataCollectionLock) {
+            if (DEBUG) {
+                Slog.v(TAG, "Sensor event " + event);
+            }
+            if (!mLastSensorReadings.isEmpty()
+                    && event.timestamp < mLastSensorReadings.getLast().timestamp) {
+                // Ignore event that came out of order.
+                return;
+            }
+            LightData data = null;
+            while (!mLastSensorReadings.isEmpty()
+                    && mLastSensorReadings.getFirst().timestamp < horizon) {
+                // Remove data that has fallen out of the window.
+                data = mLastSensorReadings.removeFirst();
+            }
+            // We put back the last one we removed so we know how long
+            // the first sensor reading was valid for.
+            if (data != null) {
+                mLastSensorReadings.addFirst(data);
+            }
+
+            data = new LightData();
+            data.timestamp = event.timestamp;
+            data.lux = event.values[0];
+            mLastSensorReadings.addLast(data);
+        }
+    }
+
+    private void batteryLevelChanged(int level, int scale) {
+        synchronized (mDataCollectionLock) {
+            mLastBatteryLevel = (float) level / (float) scale;
+        }
+    }
+
+    private final class SensorListener implements SensorEventListener {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            recordSensorEvent(event);
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+
+        }
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        public SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (DEBUG) {
+                Slog.v(TAG, "settings change " + uri);
+            }
+            // Self change is based on observer passed to notifyObserver, SettingsProvider
+            // passes null so no changes are self changes.
+            handleBrightnessChanged();
+        }
+    }
+
+    private final class Receiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Slog.d(TAG, "Received " + intent.getAction());
+            }
+            String action = intent.getAction();
+            if (Intent.ACTION_SHUTDOWN.equals(action)) {
+                stop();
+                scheduleWriteEvents();
+            } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+                int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+                int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+                if (level != -1 && scale != 0) {
+                    batteryLevelChanged(level, scale);
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        public void registerSensorListener(Context context,
+                SensorEventListener sensorListener) {
+            SensorManager sensorManager = context.getSystemService(SensorManager.class);
+            Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+            sensorManager.registerListener(sensorListener,
+                    lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
+        }
+
+        public void unregisterSensorListener(Context context, SensorEventListener sensorListener) {
+            SensorManager sensorManager = context.getSystemService(SensorManager.class);
+            sensorManager.unregisterListener(sensorListener);
+        }
+
+        public void registerBrightnessObserver(ContentResolver resolver,
+                ContentObserver settingsObserver) {
+            resolver.registerContentObserver(Settings.System.getUriFor(
+                    Settings.System.SCREEN_BRIGHTNESS),
+                    false, settingsObserver, UserHandle.USER_ALL);
+        }
+
+        public void unregisterBrightnessObserver(Context context,
+                ContentObserver settingsObserver) {
+            context.getContentResolver().unregisterContentObserver(settingsObserver);
+        }
+
+        public void registerReceiver(Context context,
+                BroadcastReceiver receiver, IntentFilter filter) {
+            context.registerReceiver(receiver, filter);
+        }
+
+        public void unregisterReceiver(Context context,
+                BroadcastReceiver receiver) {
+            context.unregisterReceiver(receiver);
+        }
+
+        public Handler getBackgroundHandler() {
+            return BackgroundThread.getHandler();
+        }
+
+        public int getSystemIntForUser(ContentResolver resolver, String setting, int defaultValue,
+                int userId) {
+            return Settings.System.getIntForUser(resolver, setting, defaultValue, userId);
+        }
+
+        public void putSystemIntForUser(ContentResolver resolver, String setting, int value,
+                int userId) {
+            Settings.System.putIntForUser(resolver, setting, value, userId);
+        }
+
+        public int getSecureIntForUser(ContentResolver resolver, String setting, int defaultValue,
+                int userId) {
+            return Settings.Secure.getIntForUser(resolver, setting, defaultValue, userId);
+        }
+
+        public AtomicFile getFile() {
+            return new AtomicFile(new File(Environment.getDataSystemDeDirectory(), EVENTS_FILE));
+        }
+
+        public long currentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        public long elapsedRealtimeNanos() {
+            return SystemClock.elapsedRealtimeNanos();
+        }
+
+        public int getUserSerialNumber(UserManager userManager, int userId) {
+            return userManager.getUserSerialNumber(userId);
+        }
+
+        public int getUserId(UserManager userManager, int userSerialNumber) {
+            return userManager.getUserHandle(userSerialNumber);
+        }
+
+        public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
+            return ActivityManager.getService().getFocusedStackInfo();
+        }
+    }
+}
diff --git a/com/android/server/display/NightDisplayService.java b/com/android/server/display/ColorDisplayService.java
similarity index 97%
rename from com/android/server/display/NightDisplayService.java
rename to com/android/server/display/ColorDisplayService.java
index a7c3ff9..af8ecad 100644
--- a/com/android/server/display/NightDisplayService.java
+++ b/com/android/server/display/ColorDisplayService.java
@@ -42,7 +42,7 @@
 import android.util.Slog;
 import android.view.animation.AnimationUtils;
 
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.server.SystemService;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
@@ -60,10 +60,10 @@
 /**
  * Tints the display at night.
  */
-public final class NightDisplayService extends SystemService
-        implements NightDisplayController.Callback {
+public final class ColorDisplayService extends SystemService
+        implements ColorDisplayController.Callback {
 
-    private static final String TAG = "NightDisplayService";
+    private static final String TAG = "ColorDisplayService";
 
     /**
      * The transition time, in milliseconds, for Night Display to turn on/off.
@@ -119,12 +119,12 @@
     private ContentObserver mUserSetupObserver;
     private boolean mBootCompleted;
 
-    private NightDisplayController mController;
+    private ColorDisplayController mController;
     private ValueAnimator mColorMatrixAnimator;
     private Boolean mIsActivated;
     private AutoMode mAutoMode;
 
-    public NightDisplayService(Context context) {
+    public ColorDisplayService(Context context) {
         super(context);
         mHandler = new Handler(Looper.getMainLooper());
     }
@@ -228,7 +228,7 @@
         Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
 
         // Create a new controller for the current user and start listening for changes.
-        mController = new NightDisplayController(getContext(), mCurrentUser);
+        mController = new ColorDisplayController(getContext(), mCurrentUser);
         mController.setListener(this);
 
         setCoefficientMatrix(getContext());
@@ -293,9 +293,9 @@
             mAutoMode = null;
         }
 
-        if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
+        if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
             mAutoMode = new CustomAutoMode();
-        } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+        } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
             mAutoMode = new TwilightAutoMode();
         }
 
@@ -463,7 +463,7 @@
         return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
     }
 
-    private abstract class AutoMode implements NightDisplayController.Callback {
+    private abstract class AutoMode implements ColorDisplayController.Callback {
         public abstract void onStart();
 
         public abstract void onStop();
diff --git a/com/android/server/display/DisplayManagerService.java b/com/android/server/display/DisplayManagerService.java
index d0a1d9e..f1e2011 100644
--- a/com/android/server/display/DisplayManagerService.java
+++ b/com/android/server/display/DisplayManagerService.java
@@ -31,9 +31,11 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.hardware.SensorManager;
+import android.hardware.display.BrightnessChangeEvent;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayViewport;
@@ -58,6 +60,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.IntArray;
 import android.util.Slog;
@@ -139,6 +142,7 @@
     private static final int MSG_DELIVER_DISPLAY_EVENT = 3;
     private static final int MSG_REQUEST_TRAVERSAL = 4;
     private static final int MSG_UPDATE_VIEWPORT = 5;
+    private static final int MSG_REGISTER_BRIGHTNESS_TRACKER = 6;
 
     private final Context mContext;
     private final DisplayManagerHandler mHandler;
@@ -256,6 +260,8 @@
 
     private final Injector mInjector;
 
+    private final BrightnessTracker mBrightnessTracker;
+
     public DisplayManagerService(Context context) {
         this(context, new Injector());
     }
@@ -274,6 +280,7 @@
 
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
+        mBrightnessTracker = new BrightnessTracker(context, null);
     }
 
     public void setupSchedulerPolicies() {
@@ -350,6 +357,7 @@
         }
 
         mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
+        mHandler.sendEmptyMessage(MSG_REGISTER_BRIGHTNESS_TRACKER);
     }
 
     @VisibleForTesting
@@ -1352,6 +1360,10 @@
                             mTempExternalTouchViewport, mTempVirtualTouchViewports);
                     break;
                 }
+
+                case MSG_REGISTER_BRIGHTNESS_TRACKER:
+                    mBrightnessTracker.start();
+                    break;
             }
         }
     }
@@ -1736,6 +1748,35 @@
             }
         }
 
+        @Override // Binder call
+        public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents() {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.BRIGHTNESS_SLIDER_USAGE,
+                    "Permission to read brightness events.");
+            int userId = UserHandle.getUserId(Binder.getCallingUid());
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mBrightnessTracker.getEvents(userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override // Binder call
+        public void setBrightness(int brightness) {
+            // STOPSHIP - remove when adaptive brightness controller accepts curves.
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.BRIGHTNESS_SLIDER_USAGE,
+                    "Permission to set brightness.");
+            int userId = UserHandle.getUserId(Binder.getCallingUid());
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mBrightnessTracker.setBrightness(brightness, userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         private boolean validatePackageName(int uid, String packageName) {
             if (packageName != null) {
                 String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
diff --git a/com/android/server/display/DisplayPowerController.java b/com/android/server/display/DisplayPowerController.java
index f930b52..600bc42 100644
--- a/com/android/server/display/DisplayPowerController.java
+++ b/com/android/server/display/DisplayPowerController.java
@@ -1120,6 +1120,27 @@
             // Dismiss the black surface without fanfare.
             mPowerState.setColorFadeLevel(1.0f);
             mPowerState.dismissColorFade();
+        } else if (target == Display.STATE_ON_SUSPEND) {
+            // Want screen full-power and suspended.
+            // Wait for brightness animation to complete beforehand unless already
+            // suspended because we may not be able to change it after suspension.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+                return;
+            }
+
+            // If not already suspending, temporarily set the state to on until the
+            // screen on is unblocked, then suspend.
+            if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+                if (!setScreenState(Display.STATE_ON)) {
+                    return;
+                }
+                setScreenState(Display.STATE_ON_SUSPEND);
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
         } else {
             // Want screen off.
             mPendingScreenOff = true;
diff --git a/com/android/server/display/DisplayTransformManager.java b/com/android/server/display/DisplayTransformManager.java
index bef6898..338e331 100644
--- a/com/android/server/display/DisplayTransformManager.java
+++ b/com/android/server/display/DisplayTransformManager.java
@@ -29,7 +29,7 @@
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
 
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import java.util.Arrays;
 
 /**
@@ -223,13 +223,13 @@
     }
 
     public boolean setColorMode(int colorMode) {
-        if (colorMode == NightDisplayController.COLOR_MODE_NATURAL) {
+        if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setNativeMode(false);
-        } else if (colorMode == NightDisplayController.COLOR_MODE_BOOSTED) {
+        } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
             applySaturation(COLOR_SATURATION_BOOSTED);
             setNativeMode(false);
-        } else if (colorMode == NightDisplayController.COLOR_MODE_SATURATED) {
+        } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
             applySaturation(COLOR_SATURATION_NATURAL);
             setNativeMode(true);
         }
diff --git a/com/android/server/display/LocalDisplayAdapter.java b/com/android/server/display/LocalDisplayAdapter.java
index d61a418..eb9ff58 100644
--- a/com/android/server/display/LocalDisplayAdapter.java
+++ b/com/android/server/display/LocalDisplayAdapter.java
@@ -22,6 +22,7 @@
 import com.android.server.lights.LightsManager;
 
 import android.content.Context;
+import android.hardware.sidekick.SidekickInternal;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -35,7 +36,6 @@
 import android.view.DisplayEventReceiver;
 import android.view.Surface;
 import android.view.SurfaceControl;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -148,6 +148,8 @@
                 return SurfaceControl.POWER_MODE_DOZE;
             case Display.STATE_DOZE_SUSPEND:
                 return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
+            case Display.STATE_ON_SUSPEND:
+                return SurfaceControl.POWER_MODE_ON_SUSPEND;
             default:
                 return SurfaceControl.POWER_MODE_NORMAL;
         }
@@ -170,6 +172,8 @@
         private int mActiveColorMode;
         private boolean mActiveColorModeInvalid;
         private Display.HdrCapabilities mHdrCapabilities;
+        private boolean mSidekickActive;
+        private SidekickInternal mSidekickInternal;
 
         private  SurfaceControl.PhysicalDisplayInfo mDisplayInfos[];
 
@@ -181,6 +185,7 @@
             updatePhysicalDisplayInfoLocked(physicalDisplayInfos, activeDisplayInfo,
                     colorModes, activeColorMode);
             updateColorModesLocked(colorModes, activeColorMode);
+            mSidekickInternal = LocalServices.getService(SidekickInternal.class);
             if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
                 LightsManager lights = LocalServices.getService(LightsManager.class);
                 mBacklight = lights.getLight(LightsManager.LIGHT_ID_BACKLIGHT);
@@ -467,6 +472,10 @@
                                     || oldState == Display.STATE_DOZE_SUSPEND) {
                                 setDisplayState(Display.STATE_DOZE);
                                 currentState = Display.STATE_DOZE;
+                            } else if (state == Display.STATE_ON_SUSPEND
+                                    || oldState == Display.STATE_ON_SUSPEND) {
+                                setDisplayState(Display.STATE_ON);
+                                currentState = Display.STATE_ON;
                             } else {
                                 return; // old state and new state is off
                             }
@@ -510,16 +519,40 @@
                                     + ", state=" + Display.stateToString(state) + ")");
                         }
 
+                        // We must tell sidekick to stop controlling the display before we
+                        // can change its power mode, so do that first.
+                        if (mSidekickActive) {
+                            Trace.traceBegin(Trace.TRACE_TAG_POWER,
+                                    "SidekickInternal#endDisplayControl");
+                            try {
+                                mSidekickInternal.endDisplayControl();
+                            } finally {
+                                Trace.traceEnd(Trace.TRACE_TAG_POWER);
+                            }
+                            mSidekickActive = false;
+                        }
+                        final int mode = getPowerModeForState(state);
                         Trace.traceBegin(Trace.TRACE_TAG_POWER, "setDisplayState("
                                 + "id=" + displayId
                                 + ", state=" + Display.stateToString(state) + ")");
                         try {
-                            final int mode = getPowerModeForState(state);
                             SurfaceControl.setDisplayPowerMode(token, mode);
                             Trace.traceCounter(Trace.TRACE_TAG_POWER, "DisplayPowerMode", mode);
                         } finally {
                             Trace.traceEnd(Trace.TRACE_TAG_POWER);
                         }
+                        // If we're entering a suspended (but not OFF) power state and we
+                        // have a sidekick available, tell it now that it can take control.
+                        if (Display.isSuspendedState(state) && state != Display.STATE_OFF
+                                && mSidekickInternal != null && !mSidekickActive) {
+                            Trace.traceBegin(Trace.TRACE_TAG_POWER,
+                                    "SidekickInternal#startDisplayControl");
+                            try {
+                                mSidekickActive = mSidekickInternal.startDisplayControl(state);
+                            } finally {
+                                Trace.traceEnd(Trace.TRACE_TAG_POWER);
+                            }
+                        }
                     }
 
                     private void setDisplayBrightness(int brightness) {
diff --git a/com/android/server/ethernet/EthernetNetworkFactory.java b/com/android/server/ethernet/EthernetNetworkFactory.java
index 2d54fd2..0bbe3b5 100644
--- a/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -427,6 +427,8 @@
         mNetworkCapabilities = new NetworkCapabilities();
         mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
         mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
         // We have no useful data on bandwidth. Say 100M up and 100M down. :-(
         mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100 * 1000);
diff --git a/com/android/server/job/JobPackageTracker.java b/com/android/server/job/JobPackageTracker.java
index 025ff0b..296743b 100644
--- a/com/android/server/job/JobPackageTracker.java
+++ b/com/android/server/job/JobPackageTracker.java
@@ -16,15 +16,19 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sSystemClock;
+import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
+
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+
 import com.android.internal.util.RingBufferIndices;
 import com.android.server.job.controllers.JobStatus;
 
@@ -57,7 +61,7 @@
     public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason) {
         int index = mEventIndices.add();
         mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK);
-        mEventTimes[index] = SystemClock.elapsedRealtime();
+        mEventTimes[index] = sElapsedRealtimeClock.millis();
         mEventUids[index] = uid;
         mEventTags[index] = tag;
         mEventJobIds[index] = jobId;
@@ -125,9 +129,9 @@
         }
 
         public DataSet() {
-            mStartUptimeTime = SystemClock.uptimeMillis();
-            mStartElapsedTime = SystemClock.elapsedRealtime();
-            mStartClockTime = System.currentTimeMillis();
+            mStartUptimeTime = sUptimeMillisClock.millis();
+            mStartElapsedTime = sElapsedRealtimeClock.millis();
+            mStartClockTime = sSystemClock.millis();
         }
 
         private PackageEntry getOrCreateEntry(int uid, String pkg) {
@@ -376,20 +380,20 @@
     }
 
     public void notePending(JobStatus job) {
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         job.madePending = now;
         rebatchIfNeeded(now);
         mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
     }
 
     public void noteNonpending(JobStatus job) {
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
         rebatchIfNeeded(now);
     }
 
     public void noteActive(JobStatus job) {
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         job.madeActive = now;
         rebatchIfNeeded(now);
         if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
@@ -402,7 +406,7 @@
     }
 
     public void noteInactive(JobStatus job, int stopReason) {
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
             mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
                     stopReason);
@@ -431,7 +435,7 @@
         if (cur == null && last == null) {
             return 0;
         }
-        final long now = SystemClock.uptimeMillis();
+        final long now = sUptimeMillisClock.millis();
         long time = 0;
         if (cur != null) {
             time += cur.getActiveTime(now) + cur.getPendingTime(now);
@@ -445,8 +449,8 @@
     }
 
     public void dump(PrintWriter pw, String prefix, int filterUid) {
-        final long now = SystemClock.uptimeMillis();
-        final long nowEllapsed = SystemClock.elapsedRealtime();
+        final long now = sUptimeMillisClock.millis();
+        final long nowEllapsed = sElapsedRealtimeClock.millis();
         final DataSet total;
         if (mLastDataSets[0] != null) {
             total = new DataSet(mLastDataSets[0]);
@@ -470,7 +474,7 @@
             return false;
         }
         pw.println("  Job history:");
-        final long now = SystemClock.elapsedRealtime();
+        final long now = sElapsedRealtimeClock.millis();
         for (int i=0; i<size; i++) {
             final int index = mEventIndices.indexOf(i);
             final int uid = mEventUids[index];
diff --git a/com/android/server/job/JobSchedulerService.java b/com/android/server/job/JobSchedulerService.java
index 78aa2f9..b549768 100644
--- a/com/android/server/job/JobSchedulerService.java
+++ b/com/android/server/job/JobSchedulerService.java
@@ -19,24 +19,15 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.IUidObserver;
+import android.app.job.IJobScheduler;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
-import android.app.job.IJobScheduler;
 import android.app.job.JobWorkItem;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -46,8 +37,8 @@
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.BatteryStats;
@@ -55,8 +46,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Process;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -71,6 +62,7 @@
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.util.ArrayUtils;
@@ -93,6 +85,16 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
 /**
  * Responsible for taking jobs representing work to be performed by a client app, and determining
  * based on the criteria specified when that job should be run against the client application's
@@ -117,6 +119,12 @@
     /** The maximum number of jobs that we allow an unprivileged app to schedule */
     private static final int MAX_JOBS_PER_APP = 100;
 
+    @VisibleForTesting
+    public static Clock sSystemClock = Clock.systemUTC();
+    @VisibleForTesting
+    public static Clock sUptimeMillisClock = SystemClock.uptimeMillisClock();
+    @VisibleForTesting
+    public static Clock sElapsedRealtimeClock = SystemClock.elapsedRealtimeClock();
 
     /** Global local for all job scheduler state. */
     final Object mLock = new Object();
@@ -960,7 +968,7 @@
             if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
                 // When we reach clock sanity, recalculate the temporal windows
                 // of all affected jobs.
-                if (mJobs.clockNowValidToInflate(System.currentTimeMillis())) {
+                if (mJobs.clockNowValidToInflate(sSystemClock.millis())) {
                     Slog.i(TAG, "RTC now valid; recalculating persisted job windows");
 
                     // We've done our job now, so stop watching the time.
@@ -1068,7 +1076,7 @@
         if (!jobStatus.isPreparedLocked()) {
             Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
         }
-        jobStatus.enqueueTime = SystemClock.elapsedRealtime();
+        jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
         final boolean update = mJobs.add(jobStatus);
         if (mReadyToRock) {
             for (int i = 0; i < mControllers.size(); i++) {
@@ -1156,7 +1164,7 @@
      * @see #maybeQueueReadyJobsForExecutionLocked
      */
     private JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
-        final long elapsedNowMillis = SystemClock.elapsedRealtime();
+        final long elapsedNowMillis = sElapsedRealtimeClock.millis();
         final JobInfo job = failureToReschedule.getJob();
 
         final long initialBackoffMillis = job.getInitialBackoffMillis();
@@ -1200,7 +1208,7 @@
                 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
         JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
                 JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
-                failureToReschedule.getLastSuccessfulRunTime(), System.currentTimeMillis());
+                failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
         for (int ic=0; ic<mControllers.size(); ic++) {
             StateController controller = mControllers.get(ic);
             controller.rescheduleForFailureLocked(newJob, failureToReschedule);
@@ -1220,7 +1228,7 @@
      * recurring job.
      */
     private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
-        final long elapsedNow = SystemClock.elapsedRealtime();
+        final long elapsedNow = sElapsedRealtimeClock.millis();
         // Compute how much of the period is remaining.
         long runEarly = 0L;
 
@@ -1239,7 +1247,7 @@
         }
         return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
                 newLatestRuntimeElapsed, 0 /* backoffAttempt */,
-                System.currentTimeMillis() /* lastSuccessfulRunTime */,
+                sSystemClock.millis() /* lastSuccessfulRunTime */,
                 periodicToReschedule.getLastFailedRunTime());
     }
 
@@ -2367,8 +2375,8 @@
         }
 
         final int filterUidFinal = UserHandle.getAppId(filterUid);
-        final long nowElapsed = SystemClock.elapsedRealtime();
-        final long nowUptime = SystemClock.uptimeMillis();
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        final long nowUptime = sUptimeMillisClock.millis();
         synchronized (mLock) {
             mConstants.dump(pw);
             pw.println();
diff --git a/com/android/server/job/JobServiceContext.java b/com/android/server/job/JobServiceContext.java
index a7e674b..ac7297e 100644
--- a/com/android/server/job/JobServiceContext.java
+++ b/com/android/server/job/JobServiceContext.java
@@ -16,11 +16,13 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.app.ActivityManager;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
 import android.app.job.IJobCallback;
 import android.app.job.IJobService;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
 import android.app.job.JobWorkItem;
 import android.content.ComponentName;
 import android.content.Context;
@@ -34,7 +36,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Slog;
@@ -202,7 +203,7 @@
             mRunningCallback = new JobCallback();
             final boolean isDeadlineExpired =
                     job.hasDeadlineConstraint() &&
-                            (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
+                            (job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
             Uri[] triggeredUris = null;
             if (job.changedUris != null) {
                 triggeredUris = new Uri[job.changedUris.size()];
@@ -217,7 +218,7 @@
             mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
                     isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
-            mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
+            mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
 
             // Once we'e begun executing a job, we by definition no longer care whether
             // it was inflated from disk with not-yet-coherent delay/deadline bounds.
@@ -428,7 +429,7 @@
             sb.append("Caller no longer running");
             if (cb.mStoppedReason != null) {
                 sb.append(", last stopped ");
-                TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb);
+                TimeUtils.formatDuration(sElapsedRealtimeClock.millis() - cb.mStoppedTime, sb);
                 sb.append(" because: ");
                 sb.append(cb.mStoppedReason);
             }
@@ -457,7 +458,7 @@
                             sb.append("Ignoring timeout of no longer active job");
                             if (jc.mStoppedReason != null) {
                                 sb.append(", stopped ");
-                                TimeUtils.formatDuration(SystemClock.elapsedRealtime()
+                                TimeUtils.formatDuration(sElapsedRealtimeClock.millis()
                                         - jc.mStoppedTime, sb);
                                 sb.append(" because: ");
                                 sb.append(jc.mStoppedReason);
@@ -740,7 +741,7 @@
     private void applyStoppedReasonLocked(String reason) {
         if (reason != null && mStoppedReason == null) {
             mStoppedReason = reason;
-            mStoppedTime = SystemClock.elapsedRealtime();
+            mStoppedTime = sElapsedRealtimeClock.millis();
             if (mRunningCallback != null) {
                 mRunningCallback.mStoppedReason = mStoppedReason;
                 mRunningCallback.mStoppedTime = mStoppedTime;
@@ -777,7 +778,7 @@
         }
         Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);
         mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
-        mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
+        mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
     }
 
 
diff --git a/com/android/server/job/JobStore.java b/com/android/server/job/JobStore.java
index aa9f77c..1af3b39 100644
--- a/com/android/server/job/JobStore.java
+++ b/com/android/server/job/JobStore.java
@@ -16,20 +16,23 @@
 
 package com.android.server.job;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sSystemClock;
+
 import android.app.ActivityManager;
 import android.app.IActivityManager;
-import android.content.ComponentName;
 import android.app.job.JobInfo;
+import android.content.ComponentName;
 import android.content.Context;
+import android.net.NetworkRequest;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.PersistableBundle;
 import android.os.Process;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
-import android.util.AtomicFile;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -37,11 +40,16 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.BitUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.IoThread;
 import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
 import com.android.server.job.controllers.JobStatus;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -53,10 +61,6 @@
 import java.util.List;
 import java.util.Set;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
 /**
  * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
  * reference, so none of the functions in this class should make a copy.
@@ -141,7 +145,7 @@
         // a *correct* timestamp, see a bunch of overdue jobs, and run them; then
         // settle into normal operation.
         mXmlTimestamp = mJobsFile.getLastModifiedTime();
-        mRtcGood = (System.currentTimeMillis() > mXmlTimestamp);
+        mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
 
         readJobMapFromDisk(mJobSet, mRtcGood);
     }
@@ -161,7 +165,7 @@
      */
     public void getRtcCorrectedJobsLocked(final ArrayList<JobStatus> toAdd,
             final ArrayList<JobStatus> toRemove) {
-        final long elapsedNow = SystemClock.elapsedRealtime();
+        final long elapsedNow = sElapsedRealtimeClock.millis();
 
         // Find the jobs that need to be fixed up, collecting them for post-iteration
         // replacement with their new versions
@@ -323,7 +327,7 @@
     private final Runnable mWriteRunnable = new Runnable() {
         @Override
         public void run() {
-            final long startElapsed = SystemClock.elapsedRealtime();
+            final long startElapsed = sElapsedRealtimeClock.millis();
             final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
             synchronized (mLock) {
                 // Clone the jobs so we can release the lock before writing.
@@ -338,7 +342,7 @@
             }
             writeJobsMapImpl(storeCopy);
             if (DEBUG) {
-                Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
+                Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
                         - startElapsed) + "ms");
             }
         }
@@ -454,17 +458,12 @@
          */
         private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
             out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
-            if (jobStatus.needsAnyConnectivity()) {
-                out.attribute(null, "connectivity", Boolean.toString(true));
-            }
-            if (jobStatus.needsMeteredConnectivity()) {
-                out.attribute(null, "metered", Boolean.toString(true));
-            }
-            if (jobStatus.needsUnmeteredConnectivity()) {
-                out.attribute(null, "unmetered", Boolean.toString(true));
-            }
-            if (jobStatus.needsNonRoamingConnectivity()) {
-                out.attribute(null, "not-roaming", Boolean.toString(true));
+            if (jobStatus.hasConnectivityConstraint()) {
+                final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
+                out.attribute(null, "net-capabilities", Long.toString(
+                        BitUtils.packBits(network.networkCapabilities.getCapabilities())));
+                out.attribute(null, "net-transport-types", Long.toString(
+                        BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
             }
             if (jobStatus.hasIdleConstraint()) {
                 out.attribute(null, "idle", Boolean.toString(true));
@@ -497,8 +496,8 @@
                 Slog.i(TAG, "storing original UTC timestamps for " + jobStatus);
             }
 
-            final long nowRTC = System.currentTimeMillis();
-            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long nowRTC = sSystemClock.millis();
+            final long nowElapsed = sElapsedRealtimeClock.millis();
             if (jobStatus.hasDeadlineConstraint()) {
                 // Wall clock deadline.
                 final long deadlineWallclock = (utcJobTimes == null)
@@ -538,7 +537,7 @@
      */
     private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes,
             long nowElapsed) {
-        final long nowWallclock = System.currentTimeMillis();
+        final long nowWallclock = sSystemClock.millis();
         final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME)
                 ? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0)
                 : JobStatus.NO_EARLIEST_RUNTIME;
@@ -581,7 +580,7 @@
                 synchronized (mLock) {
                     jobs = readJobMapImpl(fis, rtcGood);
                     if (jobs != null) {
-                        long now = SystemClock.elapsedRealtime();
+                        long now = sElapsedRealtimeClock.millis();
                         IActivityManager am = ActivityManager.getService();
                         for (int i=0; i<jobs.size(); i++) {
                             JobStatus js = jobs.get(i);
@@ -754,7 +753,7 @@
                 return null;
             }
 
-            final long elapsedNow = SystemClock.elapsedRealtime();
+            final long elapsedNow = sElapsedRealtimeClock.millis();
             Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
 
             if (XML_TAG_PERIODIC.equals(parser.getName())) {
@@ -862,22 +861,38 @@
         }
 
         private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
-            String val = parser.getAttributeValue(null, "connectivity");
-            if (val != null) {
-                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+            String val;
+
+            final String netCapabilities = parser.getAttributeValue(null, "net-capabilities");
+            final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
+            if (netCapabilities != null && netTransportTypes != null) {
+                final NetworkRequest request = new NetworkRequest.Builder().build();
+                // We're okay throwing NFE here; caught by caller
+                request.networkCapabilities.setCapabilities(
+                        BitUtils.unpackBits(Long.parseLong(netCapabilities)));
+                request.networkCapabilities.setTransportTypes(
+                        BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
+                jobBuilder.setRequiredNetwork(request);
+            } else {
+                // Read legacy values
+                val = parser.getAttributeValue(null, "connectivity");
+                if (val != null) {
+                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+                }
+                val = parser.getAttributeValue(null, "metered");
+                if (val != null) {
+                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
+                }
+                val = parser.getAttributeValue(null, "unmetered");
+                if (val != null) {
+                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+                }
+                val = parser.getAttributeValue(null, "not-roaming");
+                if (val != null) {
+                    jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
+                }
             }
-            val = parser.getAttributeValue(null, "metered");
-            if (val != null) {
-                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
-            }
-            val = parser.getAttributeValue(null, "unmetered");
-            if (val != null) {
-                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
-            }
-            val = parser.getAttributeValue(null, "not-roaming");
-            if (val != null) {
-                jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
-            }
+
             val = parser.getAttributeValue(null, "idle");
             if (val != null) {
                 jobBuilder.setRequiresDeviceIdle(true);
@@ -937,8 +952,8 @@
         private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser)
                 throws NumberFormatException {
             // Pull out execution time data.
-            final long nowWallclock = System.currentTimeMillis();
-            final long nowElapsed = SystemClock.elapsedRealtime();
+            final long nowWallclock = sSystemClock.millis();
+            final long nowElapsed = sElapsedRealtimeClock.millis();
 
             long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME;
             long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME;
diff --git a/com/android/server/job/controllers/AppIdleController.java b/com/android/server/job/controllers/AppIdleController.java
index 39f2a96..caa8522 100644
--- a/com/android/server/job/controllers/AppIdleController.java
+++ b/com/android/server/job/controllers/AppIdleController.java
@@ -174,7 +174,7 @@
     private final class AppIdleStateChangeListener
             extends UsageStatsManagerInternal.AppIdleStateChangeListener {
         @Override
-        public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+        public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) {
             boolean changed = false;
             synchronized (mLock) {
                 if (mAppIdleParoleOn) {
diff --git a/com/android/server/job/controllers/BatteryController.java b/com/android/server/job/controllers/BatteryController.java
index 9111969..76ff834 100644
--- a/com/android/server/job/controllers/BatteryController.java
+++ b/com/android/server/job/controllers/BatteryController.java
@@ -16,13 +16,14 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.BatteryManager;
 import android.os.BatteryManagerInternal;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -203,7 +204,7 @@
                 if (Intent.ACTION_BATTERY_LOW.equals(action)) {
                     if (DEBUG) {
                         Slog.d(TAG, "Battery life too low to do work. @ "
-                                + SystemClock.elapsedRealtime());
+                                + sElapsedRealtimeClock.millis());
                     }
                     // If we get this action, the battery is discharging => it isn't plugged in so
                     // there's no work to cancel. We track this variable for the case where it is
@@ -213,14 +214,14 @@
                 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
                     if (DEBUG) {
                         Slog.d(TAG, "Battery life healthy enough to do work. @ "
-                                + SystemClock.elapsedRealtime());
+                                + sElapsedRealtimeClock.millis());
                     }
                     mBatteryHealthy = true;
                     maybeReportNewChargingStateLocked();
                 } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
                     if (DEBUG) {
                         Slog.d(TAG, "Received charging intent, fired @ "
-                                + SystemClock.elapsedRealtime());
+                                + sElapsedRealtimeClock.millis());
                     }
                     mCharging = true;
                     maybeReportNewChargingStateLocked();
diff --git a/com/android/server/job/controllers/ConnectivityController.java b/com/android/server/job/controllers/ConnectivityController.java
index ddee345..da28769 100644
--- a/com/android/server/job/controllers/ConnectivityController.java
+++ b/com/android/server/job/controllers/ConnectivityController.java
@@ -54,7 +54,6 @@
     private final ConnectivityManager mConnManager;
     private final NetworkPolicyManager mNetPolicyManager;
     private boolean mConnected;
-    private boolean mValidated;
 
     @GuardedBy("mLock")
     private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
@@ -79,7 +78,7 @@
         mConnManager = mContext.getSystemService(ConnectivityManager.class);
         mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
 
-        mConnected = mValidated = false;
+        mConnected = false;
 
         mConnManager.registerDefaultNetworkCallback(mNetworkCallback);
         mNetPolicyManager.registerListener(mNetPolicyListener);
@@ -149,32 +148,21 @@
     }
 
     private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
+        // TODO: consider matching against non-active networks
+
         final int jobUid = jobStatus.getSourceUid();
         final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
         final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
         final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked);
-
-        final NetworkCapabilities capabilities = (network != null)
-                ? mConnManager.getNetworkCapabilities(network) : null;
+        final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
 
         final boolean connected = (info != null) && info.isConnected();
-        final boolean validated = (capabilities != null)
-                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+        final boolean satisfied = jobStatus.getJob().getRequiredNetwork().networkCapabilities
+                .satisfiedByNetworkCapabilities(capabilities);
         final boolean sane = isSane(jobStatus, capabilities);
-        final boolean connectionUsable = connected && validated && sane;
 
-        final boolean metered = connected && (capabilities != null)
-                && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-        final boolean unmetered = connected && (capabilities != null)
-                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-        final boolean notRoaming = connected && (info != null)
-                && !info.isRoaming();
-
-        boolean changed = false;
-        changed |= jobStatus.setConnectivityConstraintSatisfied(connectionUsable);
-        changed |= jobStatus.setMeteredConstraintSatisfied(metered);
-        changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
-        changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
+        final boolean changed = jobStatus
+                .setConnectivityConstraintSatisfied(connected && satisfied && sane);
 
         // Pass along the evaluated network for job to use; prevents race
         // conditions as default routes change over time, and opens the door to
@@ -185,17 +173,13 @@
         // overall state of connectivity constraint satisfiability.
         if (jobUid == Process.SYSTEM_UID) {
             mConnected = connected;
-            mValidated = validated;
         }
 
         if (DEBUG) {
             Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
-                    + " for " + jobStatus + ": usable=" + connectionUsable
-                    + " connected=" + connected
-                    + " validated=" + validated
-                    + " metered=" + metered
-                    + " unmetered=" + unmetered
-                    + " notRoaming=" + notRoaming);
+                    + " for " + jobStatus + ": connected=" + connected
+                    + " satisfied=" + satisfied
+                    + " sane=" + sane);
         }
         return changed;
     }
@@ -292,8 +276,6 @@
     public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
         pw.print("Connectivity: connected=");
         pw.print(mConnected);
-        pw.print(" validated=");
-        pw.println(mValidated);
         pw.print("Tracking ");
         pw.print(mTrackedJobs.size());
         pw.println(":");
@@ -304,10 +286,7 @@
                 js.printUniqueId(pw);
                 pw.print(" from ");
                 UserHandle.formatUid(pw, js.getSourceUid());
-                pw.print(": C="); pw.print(js.needsAnyConnectivity());
-                pw.print(": M="); pw.print(js.needsMeteredConnectivity());
-                pw.print(": UM="); pw.print(js.needsUnmeteredConnectivity());
-                pw.print(": NR="); pw.println(js.needsNonRoamingConnectivity());
+                pw.print(": "); pw.print(js.getJob().getRequiredNetwork());
             }
         }
     }
diff --git a/com/android/server/job/controllers/IdleController.java b/com/android/server/job/controllers/IdleController.java
index 9eda046..7bde174 100644
--- a/com/android/server/job/controllers/IdleController.java
+++ b/com/android/server/job/controllers/IdleController.java
@@ -16,7 +16,7 @@
 
 package com.android.server.job.controllers;
 
-import java.io.PrintWriter;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -33,6 +32,8 @@
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateChangedListener;
 
+import java.io.PrintWriter;
+
 public final class IdleController extends StateController {
     private static final String TAG = "IdleController";
 
@@ -169,7 +170,7 @@
                 // when the screen goes off or dreaming starts, we schedule the
                 // alarm that will tell us when we have decided the device is
                 // truly idle.
-                final long nowElapsed = SystemClock.elapsedRealtime();
+                final long nowElapsed = sElapsedRealtimeClock.millis();
                 final long when = nowElapsed + mInactivityIdleThreshold;
                 if (DEBUG) {
                     Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
@@ -182,7 +183,7 @@
                 // idle time starts now. Do not set mIdle if screen is on.
                 if (!mIdle && !mScreenOn) {
                     if (DEBUG) {
-                        Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime());
+                        Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
                     }
                     mIdle = true;
                     reportNewIdleState(mIdle);
diff --git a/com/android/server/job/controllers/JobStatus.java b/com/android/server/job/controllers/JobStatus.java
index 46ed84e..a5906cb 100644
--- a/com/android/server/job/controllers/JobStatus.java
+++ b/com/android/server/job/controllers/JobStatus.java
@@ -16,6 +16,8 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.job.JobInfo;
@@ -25,7 +27,6 @@
 import android.net.Network;
 import android.net.Uri;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.format.Time;
 import android.util.ArraySet;
@@ -64,19 +65,12 @@
     static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW;
     static final int CONSTRAINT_TIMING_DELAY = 1<<31;
     static final int CONSTRAINT_DEADLINE = 1<<30;
-    static final int CONSTRAINT_UNMETERED = 1<<29;
     static final int CONSTRAINT_CONNECTIVITY = 1<<28;
     static final int CONSTRAINT_APP_NOT_IDLE = 1<<27;
     static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
     static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<25;
-    static final int CONSTRAINT_NOT_ROAMING = 1<<24;
-    static final int CONSTRAINT_METERED = 1<<23;
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1<<22;
 
-    static final int CONNECTIVITY_MASK =
-            CONSTRAINT_UNMETERED | CONSTRAINT_CONNECTIVITY |
-            CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED;
-
     // Soft override: ignore constraints like time that don't affect API availability
     public static final int OVERRIDE_SOFT = 1;
     // Full override: ignore all constraints including API-affecting like connectivity
@@ -264,27 +258,9 @@
 
         int requiredConstraints = job.getConstraintFlags();
 
-        switch (job.getNetworkType()) {
-            case JobInfo.NETWORK_TYPE_NONE:
-                // No constraint.
-                break;
-            case JobInfo.NETWORK_TYPE_ANY:
-                requiredConstraints |= CONSTRAINT_CONNECTIVITY;
-                break;
-            case JobInfo.NETWORK_TYPE_UNMETERED:
-                requiredConstraints |= CONSTRAINT_UNMETERED;
-                break;
-            case JobInfo.NETWORK_TYPE_NOT_ROAMING:
-                requiredConstraints |= CONSTRAINT_NOT_ROAMING;
-                break;
-            case JobInfo.NETWORK_TYPE_METERED:
-                requiredConstraints |= CONSTRAINT_METERED;
-                break;
-            default:
-                Slog.w(TAG, "Unrecognized networking constraint " + job.getNetworkType());
-                break;
+        if (job.getRequiredNetwork() != null) {
+            requiredConstraints |= CONSTRAINT_CONNECTIVITY;
         }
-
         if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
             requiredConstraints |= CONSTRAINT_TIMING_DELAY;
         }
@@ -365,7 +341,7 @@
      */
     public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName,
             int sourceUserId, String tag) {
-        final long elapsedNow = SystemClock.elapsedRealtime();
+        final long elapsedNow = sElapsedRealtimeClock.millis();
         final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
         if (job.isPeriodic()) {
             latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
@@ -610,25 +586,9 @@
 
     /** Does this job have any sort of networking constraint? */
     public boolean hasConnectivityConstraint() {
-        return (requiredConstraints&CONNECTIVITY_MASK) != 0;
-    }
-
-    public boolean needsAnyConnectivity() {
         return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
     }
 
-    public boolean needsUnmeteredConnectivity() {
-        return (requiredConstraints&CONSTRAINT_UNMETERED) != 0;
-    }
-
-    public boolean needsMeteredConnectivity() {
-        return (requiredConstraints&CONSTRAINT_METERED) != 0;
-    }
-
-    public boolean needsNonRoamingConnectivity() {
-        return (requiredConstraints&CONSTRAINT_NOT_ROAMING) != 0;
-    }
-
     public boolean hasChargingConstraint() {
         return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
     }
@@ -725,18 +685,6 @@
         return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
     }
 
-    boolean setUnmeteredConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_UNMETERED, state);
-    }
-
-    boolean setMeteredConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_METERED, state);
-    }
-
-    boolean setNotRoamingConstraintSatisfied(boolean state) {
-        return setConstraintSatisfied(CONSTRAINT_NOT_ROAMING, state);
-    }
-
     boolean setAppNotIdleConstraintSatisfied(boolean state) {
         return setConstraintSatisfied(CONSTRAINT_APP_NOT_IDLE, state);
     }
@@ -817,12 +765,9 @@
                 && notRestrictedInBg;
     }
 
-    static final int CONSTRAINTS_OF_INTEREST =
-            CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
-            CONSTRAINT_TIMING_DELAY |
-            CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
-            CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED |
-            CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
+    static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
+            | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
+            | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
 
     // Soft override covers all non-"functional" constraints
     static final int SOFT_OVERRIDE_CONSTRAINTS =
@@ -870,15 +815,14 @@
         sb.append(getSourceUid());
         if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME
                 || latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
-            long now = SystemClock.elapsedRealtime();
+            long now = sElapsedRealtimeClock.millis();
             sb.append(" TIME=");
             formatRunTime(sb, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, now);
             sb.append(":");
             formatRunTime(sb, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, now);
         }
-        if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
-            sb.append(" NET=");
-            sb.append(job.getNetworkType());
+        if (job.getRequiredNetwork() != null) {
+            sb.append(" NET");
         }
         if (job.isRequireCharging()) {
             sb.append(" CHARGING");
@@ -985,15 +929,6 @@
         if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
             pw.print(" CONNECTIVITY");
         }
-        if ((constraints&CONSTRAINT_UNMETERED) != 0) {
-            pw.print(" UNMETERED");
-        }
-        if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) {
-            pw.print(" NOT_ROAMING");
-        }
-        if ((constraints&CONSTRAINT_METERED) != 0) {
-            pw.print(" METERED");
-        }
         if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) {
             pw.print(" APP_NOT_IDLE");
         }
@@ -1084,11 +1019,13 @@
                 pw.print(prefix); pw.println("  Granted URI permissions:");
                 uriPerms.dump(pw, prefix + "  ");
             }
-            if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
-                pw.print(prefix); pw.print("  Network type: "); pw.println(job.getNetworkType());
+            if (job.getRequiredNetwork() != null) {
+                pw.print(prefix); pw.print("  Network type: ");
+                pw.println(job.getRequiredNetwork());
             }
             if (totalNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                pw.print(prefix); pw.print("  Network bytes: "); pw.println(totalNetworkBytes);
+                pw.print(prefix); pw.print("  Network bytes: ");
+                pw.println(totalNetworkBytes);
             }
             if (job.getMinLatencyMillis() != 0) {
                 pw.print(prefix); pw.print("  Minimum latency: ");
diff --git a/com/android/server/job/controllers/StorageController.java b/com/android/server/job/controllers/StorageController.java
index c24e563..84782f5 100644
--- a/com/android/server/job/controllers/StorageController.java
+++ b/com/android/server/job/controllers/StorageController.java
@@ -16,11 +16,12 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -154,13 +155,13 @@
             if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Available storage too low to do work. @ "
-                            + SystemClock.elapsedRealtime());
+                            + sElapsedRealtimeClock.millis());
                 }
                 mStorageLow = true;
             } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Available stoage high enough to do work. @ "
-                            + SystemClock.elapsedRealtime());
+                            + sElapsedRealtimeClock.millis());
                 }
                 mStorageLow = false;
                 maybeReportNewStorageState();
diff --git a/com/android/server/job/controllers/TimeController.java b/com/android/server/job/controllers/TimeController.java
index ee4c606..cb9e43a 100644
--- a/com/android/server/job/controllers/TimeController.java
+++ b/com/android/server/job/controllers/TimeController.java
@@ -16,10 +16,11 @@
 
 package com.android.server.job.controllers;
 
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
 import android.app.AlarmManager;
 import android.app.AlarmManager.OnAlarmListener;
 import android.content.Context;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Slog;
@@ -84,7 +85,7 @@
             // pattern of having a job with a 0 deadline constraint ("run immediately").
             // Unlike most controllers, once one of our constraints has been satisfied, it
             // will never be unsatisfied (our time base can not go backwards).
-            final long nowElapsedMillis = SystemClock.elapsedRealtime();
+            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
             if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
                 return;
             } else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
@@ -157,7 +158,7 @@
             long nextExpiryTime = Long.MAX_VALUE;
             int nextExpiryUid = 0;
             String nextExpiryPackageName = null;
-            final long nowElapsedMillis = SystemClock.elapsedRealtime();
+            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
 
             Iterator<JobStatus> it = mTrackedJobs.iterator();
             while (it.hasNext()) {
@@ -201,7 +202,7 @@
      */
     private void checkExpiredDelaysAndResetAlarm() {
         synchronized (mLock) {
-            final long nowElapsedMillis = SystemClock.elapsedRealtime();
+            final long nowElapsedMillis = sElapsedRealtimeClock.millis();
             long nextDelayTime = Long.MAX_VALUE;
             int nextDelayUid = 0;
             String nextDelayPackageName = null;
@@ -283,7 +284,7 @@
     }
 
     private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
-        final long earliestWakeupTimeElapsed = SystemClock.elapsedRealtime();
+        final long earliestWakeupTimeElapsed = sElapsedRealtimeClock.millis();
         if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
             return earliestWakeupTimeElapsed;
         }
@@ -328,9 +329,9 @@
 
     @Override
     public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
-        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long nowElapsed = sElapsedRealtimeClock.millis();
         pw.print("Alarms: now=");
-        pw.print(SystemClock.elapsedRealtime());
+        pw.print(sElapsedRealtimeClock.millis());
         pw.println();
         pw.print("Next delay alarm in ");
         TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
diff --git a/com/android/server/location/ActivityRecognitionProxy.java b/com/android/server/location/ActivityRecognitionProxy.java
index 55222dc..f82b976 100644
--- a/com/android/server/location/ActivityRecognitionProxy.java
+++ b/com/android/server/location/ActivityRecognitionProxy.java
@@ -103,51 +103,55 @@
      * Helper function to bind the FusedLocationHardware to the appropriate FusedProvider instance.
      */
     private void bindProvider() {
-        IBinder binder = mServiceWatcher.getBinder();
-        if (binder == null) {
-            Log.e(TAG, "Null binder found on connection.");
-            return;
-        }
-        String descriptor;
-        try {
-            descriptor = binder.getInterfaceDescriptor();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to get interface descriptor.", e);
-            return;
-        }
+        if (!mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                String descriptor;
+                try {
+                    descriptor = binder.getInterfaceDescriptor();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to get interface descriptor.", e);
+                    return;
+                }
 
-        if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) {
-            IActivityRecognitionHardwareWatcher watcher =
-                    IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
-            if (watcher == null) {
-                Log.e(TAG, "No watcher found on connection.");
-                return;
+                if (IActivityRecognitionHardwareWatcher.class.getCanonicalName()
+                        .equals(descriptor)) {
+                    IActivityRecognitionHardwareWatcher watcher =
+                            IActivityRecognitionHardwareWatcher.Stub.asInterface(binder);
+                    if (watcher == null) {
+                        Log.e(TAG, "No watcher found on connection.");
+                        return;
+                    }
+                    if (mInstance == null) {
+                        // to keep backwards compatibility do not update the watcher when there is
+                        // no instance available, or it will cause an NPE
+                        Log.d(TAG, "AR HW instance not available, binding will be a no-op.");
+                        return;
+                    }
+                    try {
+                        watcher.onInstanceChanged(mInstance);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error delivering hardware interface to watcher.", e);
+                    }
+                } else if (IActivityRecognitionHardwareClient.class.getCanonicalName()
+                            .equals(descriptor)) {
+                    IActivityRecognitionHardwareClient client =
+                            IActivityRecognitionHardwareClient.Stub.asInterface(binder);
+                    if (client == null) {
+                        Log.e(TAG, "No client found on connection.");
+                        return;
+                    }
+                    try {
+                        client.onAvailabilityChanged(mIsSupported, mInstance);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Error delivering hardware interface to client.", e);
+                    }
+                } else {
+                    Log.e(TAG, "Invalid descriptor found on connection: " + descriptor);
+                }
             }
-            if (mInstance == null) {
-                // to keep backwards compatibility do not update the watcher when there is no
-                // instance available, or it will cause an NPE
-                Log.d(TAG, "AR HW instance not available, binding will be a no-op.");
-                return;
-            }
-            try {
-                watcher.onInstanceChanged(mInstance);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error delivering hardware interface to watcher.", e);
-            }
-        } else if (IActivityRecognitionHardwareClient.class.getCanonicalName().equals(descriptor)) {
-            IActivityRecognitionHardwareClient client =
-                    IActivityRecognitionHardwareClient.Stub.asInterface(binder);
-            if (client == null) {
-                Log.e(TAG, "No client found on connection.");
-                return;
-            }
-            try {
-                client.onAvailabilityChanged(mIsSupported, mInstance);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error delivering hardware interface to client.", e);
-            }
-        } else {
-            Log.e(TAG, "Invalid descriptor found on connection: " + descriptor);
+        })) {
+            Log.e(TAG, "Null binder found on connection.");
         }
     }
 }
diff --git a/com/android/server/location/FusedProxy.java b/com/android/server/location/FusedProxy.java
index f7fac77..b90f037 100644
--- a/com/android/server/location/FusedProxy.java
+++ b/com/android/server/location/FusedProxy.java
@@ -23,6 +23,7 @@
 import android.hardware.location.IFusedLocationHardware;
 import android.location.IFusedProvider;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -112,17 +113,18 @@
      * @param locationHardware  The FusedLocationHardware instance to use for the binding operation.
      */
     private void bindProvider(IFusedLocationHardware locationHardware) {
-        IFusedProvider provider = IFusedProvider.Stub.asInterface(mServiceWatcher.getBinder());
-
-        if (provider == null) {
+        if (!mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                IFusedProvider provider = IFusedProvider.Stub.asInterface(binder);
+                try {
+                    provider.onFusedLocationHardwareChange(locationHardware);
+                } catch (RemoteException e) {
+                    Log.e(TAG, e.toString());
+                }
+            }
+        })) {
             Log.e(TAG, "No instance of FusedProvider found on FusedLocationHardware connected.");
-            return;
-        }
-
-        try {
-            provider.onFusedLocationHardwareChange(locationHardware);
-        } catch (RemoteException e) {
-            Log.e(TAG, e.toString());
         }
     }
 }
diff --git a/com/android/server/location/GeocoderProxy.java b/com/android/server/location/GeocoderProxy.java
index 422b94b..9ad4aa1 100644
--- a/com/android/server/location/GeocoderProxy.java
+++ b/com/android/server/location/GeocoderProxy.java
@@ -21,6 +21,7 @@
 import android.location.GeocoderParams;
 import android.location.IGeocodeProvider;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -63,42 +64,47 @@
         return mServiceWatcher.start();
     }
 
-    private IGeocodeProvider getService() {
-        return IGeocodeProvider.Stub.asInterface(mServiceWatcher.getBinder());
-    }
-
     public String getConnectedPackageName() {
         return mServiceWatcher.getBestPackageName();
     }
 
     public String getFromLocation(double latitude, double longitude, int maxResults,
             GeocoderParams params, List<Address> addrs) {
-        IGeocodeProvider provider = getService();
-        if (provider != null) {
-            try {
-                return provider.getFromLocation(latitude, longitude, maxResults, params, addrs);
-            } catch (RemoteException e) {
-                Log.w(TAG, e);
+        final String[] result = new String[] {"Service not Available"};
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
+                try {
+                    result[0] = provider.getFromLocation(
+                            latitude, longitude, maxResults, params, addrs);
+                } catch (RemoteException e) {
+                    Log.w(TAG, e);
+                }
             }
-        }
-        return "Service not Available";
+        });
+        return result[0];
     }
 
     public String getFromLocationName(String locationName,
             double lowerLeftLatitude, double lowerLeftLongitude,
             double upperRightLatitude, double upperRightLongitude, int maxResults,
             GeocoderParams params, List<Address> addrs) {
-        IGeocodeProvider provider = getService();
-        if (provider != null) {
-            try {
-                return provider.getFromLocationName(locationName, lowerLeftLatitude,
-                        lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
-                        maxResults, params, addrs);
-            } catch (RemoteException e) {
-                Log.w(TAG, e);
+        final String[] result = new String[] {"Service not Available"};
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
+                try {
+                    result[0] = provider.getFromLocationName(locationName, lowerLeftLatitude,
+                            lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+                            maxResults, params, addrs);
+                } catch (RemoteException e) {
+                    Log.w(TAG, e);
+                }
             }
-        }
-        return "Service not Available";
+        });
+        return result[0];
     }
 
 }
diff --git a/com/android/server/location/GeofenceManager.java b/com/android/server/location/GeofenceManager.java
index 2493dfb..d50ffe9 100644
--- a/com/android/server/location/GeofenceManager.java
+++ b/com/android/server/location/GeofenceManager.java
@@ -145,7 +145,8 @@
      */
     private void updateMinInterval() {
         mEffectiveMinIntervalMs = Settings.Global.getLong(mResolver,
-                Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, DEFAULT_MIN_INTERVAL_MS);
+                Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
+                DEFAULT_MIN_INTERVAL_MS);
     }
 
     public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
diff --git a/com/android/server/location/GeofenceProxy.java b/com/android/server/location/GeofenceProxy.java
index b3a0010..eb47b2f 100644
--- a/com/android/server/location/GeofenceProxy.java
+++ b/com/android/server/location/GeofenceProxy.java
@@ -116,15 +116,17 @@
     };
 
     private void setGeofenceHardwareInProviderLocked() {
-        try {
-            IGeofenceProvider provider = IGeofenceProvider.Stub.asInterface(
-                      mServiceWatcher.getBinder());
-            if (provider != null) {
-                provider.setGeofenceHardware(mGeofenceHardware);
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                final IGeofenceProvider provider = IGeofenceProvider.Stub.asInterface(binder);
+                try {
+                    provider.setGeofenceHardware(mGeofenceHardware);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Remote Exception: setGeofenceHardwareInProviderLocked: " + e);
+                }
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Remote Exception: setGeofenceHardwareInProviderLocked: " + e);
-        }
+        });
     }
 
     private void setGpsGeofenceLocked() {
diff --git a/com/android/server/location/GnssLocationProvider.java b/com/android/server/location/GnssLocationProvider.java
index 4cf35bc..0b511f2 100644
--- a/com/android/server/location/GnssLocationProvider.java
+++ b/com/android/server/location/GnssLocationProvider.java
@@ -48,6 +48,7 @@
 import android.net.NetworkRequest;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerSaveState;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -84,7 +85,6 @@
 import com.android.internal.location.ProviderRequest;
 
 import com.android.server.power.BatterySaverPolicy;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
 
 import libcore.io.IoUtils;
 
@@ -803,18 +803,6 @@
             }
         };
         mGnssMetrics = new GnssMetrics();
-
-        /*
-        * A cycle of native_init() and native_cleanup() is needed so that callbacks are registered
-        * after bootup even when location is disabled. This will allow Emergency SUPL to work even
-        * when location is disabled before device restart.
-        * */
-        boolean isInitialized = native_init();
-        if(!isInitialized) {
-            Log.d(TAG, "Failed to initialize at bootup");
-        } else {
-            native_cleanup();
-        }
     }
 
     /**
@@ -1373,24 +1361,26 @@
     public boolean sendExtraCommand(String command, Bundle extras) {
 
         long identity = Binder.clearCallingIdentity();
-        boolean result = false;
+        try {
+            boolean result = false;
 
-        if ("delete_aiding_data".equals(command)) {
-            result = deleteAidingData(extras);
-        } else if ("force_time_injection".equals(command)) {
-            requestUtcTime();
-            result = true;
-        } else if ("force_xtra_injection".equals(command)) {
-            if (mSupportsXtra) {
-                xtraDownloadRequest();
+            if ("delete_aiding_data".equals(command)) {
+                result = deleteAidingData(extras);
+            } else if ("force_time_injection".equals(command)) {
+                requestUtcTime();
                 result = true;
+            } else if ("force_xtra_injection".equals(command)) {
+                if (mSupportsXtra) {
+                    xtraDownloadRequest();
+                    result = true;
+                }
+            } else {
+                Log.w(TAG, "sendExtraCommand: unknown command " + command);
             }
-        } else {
-            Log.w(TAG, "sendExtraCommand: unknown command " + command);
+            return result;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
-
-        Binder.restoreCallingIdentity(identity);
-        return result;
     }
 
     private IGpsGeofenceHardware mGpsGeofenceBinder = new IGpsGeofenceHardware.Stub() {
@@ -2272,6 +2262,19 @@
          * this handler.
          */
         private void handleInitialize() {
+            /*
+             * A cycle of native_init() and native_cleanup() is needed so that callbacks are
+             * registered after bootup even when location is disabled.
+             * This will allow Emergency SUPL to work even when location is disabled before device
+             * restart.
+             */
+            boolean isInitialized = native_init();
+            if(!isInitialized) {
+                Log.w(TAG, "Native initialization failed at bootup");
+            } else {
+                native_cleanup();
+            }
+
             // load default GPS configuration
             // (this configuration might change in the future based on SIM changes)
             reloadGpsProperties(mContext, mProperties);
diff --git a/com/android/server/location/LocationProviderProxy.java b/com/android/server/location/LocationProviderProxy.java
index b44087c..16eae62 100644
--- a/com/android/server/location/LocationProviderProxy.java
+++ b/com/android/server/location/LocationProviderProxy.java
@@ -24,6 +24,7 @@
 import android.location.LocationProvider;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.WorkSource;
 import android.util.Log;
@@ -82,10 +83,6 @@
         return mServiceWatcher.start();
     }
 
-    private ILocationProvider getService() {
-        return ILocationProvider.Stub.asInterface(mServiceWatcher.getBinder());
-    }
-
     public String getConnectedPackageName() {
         return mServiceWatcher.getBestPackageName();
     }
@@ -101,43 +98,46 @@
             if (D) Log.d(TAG, "applying state to connected service");
 
             boolean enabled;
-            ProviderProperties properties = null;
+            final ProviderProperties[] properties = new ProviderProperties[1];
             ProviderRequest request;
             WorkSource source;
-            ILocationProvider service;
             synchronized (mLock) {
                 enabled = mEnabled;
                 request = mRequest;
                 source = mWorksource;
-                service = getService();
             }
 
-            if (service == null) return;
 
-            try {
-                // load properties from provider
-                properties = service.getProperties();
-                if (properties == null) {
-                    Log.e(TAG, mServiceWatcher.getBestPackageName() +
-                            " has invalid locatino provider properties");
-                }
+            mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+                @Override
+                public void run(IBinder binder) {
+                    ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+                    try {
+                        // load properties from provider
+                        properties[0] = service.getProperties();
+                        if (properties[0] == null) {
+                            Log.e(TAG, mServiceWatcher.getBestPackageName() +
+                                    " has invalid location provider properties");
+                        }
 
-                // apply current state to new service
-                if (enabled) {
-                    service.enable();
-                    if (request != null) {
-                        service.setRequest(request, source);
+                        // apply current state to new service
+                        if (enabled) {
+                            service.enable();
+                            if (request != null) {
+                                service.setRequest(request, source);
+                            }
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, e);
+                    } catch (Exception e) {
+                        // never let remote service crash system server
+                        Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
                     }
                 }
-            } catch (RemoteException e) {
-                Log.w(TAG, e);
-            } catch (Exception e) {
-                // never let remote service crash system server
-                Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
-            }
+            });
 
             synchronized (mLock) {
-                mProperties = properties;
+                mProperties = properties[0];
             }
         }
     };
@@ -159,17 +159,20 @@
         synchronized (mLock) {
             mEnabled = true;
         }
-        ILocationProvider service = getService();
-        if (service == null) return;
-
-        try {
-            service.enable();
-        } catch (RemoteException e) {
-            Log.w(TAG, e);
-        } catch (Exception e) {
-            // never let remote service crash system server
-            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
-        }
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+                try {
+                    service.enable();
+                } catch (RemoteException e) {
+                    Log.w(TAG, e);
+                } catch (Exception e) {
+                    // never let remote service crash system server
+                    Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+                }
+            }
+        });
     }
 
     @Override
@@ -177,17 +180,20 @@
         synchronized (mLock) {
             mEnabled = false;
         }
-        ILocationProvider service = getService();
-        if (service == null) return;
-
-        try {
-            service.disable();
-        } catch (RemoteException e) {
-            Log.w(TAG, e);
-        } catch (Exception e) {
-            // never let remote service crash system server
-            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
-        }
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+                try {
+                    service.disable();
+                } catch (RemoteException e) {
+                    Log.w(TAG, e);
+                } catch (Exception e) {
+                    // never let remote service crash system server
+                    Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+                }
+            }
+        });
     }
 
     @Override
@@ -203,17 +209,20 @@
             mRequest = request;
             mWorksource = source;
         }
-        ILocationProvider service = getService();
-        if (service == null) return;
-
-        try {
-            service.setRequest(request, source);
-        } catch (RemoteException e) {
-            Log.w(TAG, e);
-        } catch (Exception e) {
-            // never let remote service crash system server
-            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
-        }
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+                try {
+                    service.setRequest(request, source);
+                } catch (RemoteException e) {
+                    Log.w(TAG, e);
+                } catch (Exception e) {
+                    // never let remote service crash system server
+                    Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+                }
+            }
+        });
     }
 
     @Override
@@ -223,66 +232,78 @@
         pw.append(" pkg=").append(mServiceWatcher.getBestPackageName());
         pw.append(" version=").append("" + mServiceWatcher.getBestVersion());
         pw.append('\n');
-
-        ILocationProvider service = getService();
-        if (service == null) {
+        if (!mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+                try {
+                    TransferPipe.dumpAsync(service.asBinder(), fd, args);
+                } catch (IOException | RemoteException e) {
+                    pw.println("Failed to dump location provider: " + e);
+                }
+            }
+        })) {
             pw.println("service down (null)");
-            return;
-        }
-        pw.flush();
-
-        try {
-            TransferPipe.dumpAsync(service.asBinder(), fd, args);
-        } catch (IOException | RemoteException e) {
-            pw.println("Failed to dump location provider: " + e);
         }
     }
 
     @Override
     public int getStatus(Bundle extras) {
-        ILocationProvider service = getService();
-        if (service == null) return LocationProvider.TEMPORARILY_UNAVAILABLE;
-
-        try {
-            return service.getStatus(extras);
-        } catch (RemoteException e) {
-            Log.w(TAG, e);
-        } catch (Exception e) {
-            // never let remote service crash system server
-            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
-        }
-        return LocationProvider.TEMPORARILY_UNAVAILABLE;
+        final int[] result = new int[] {LocationProvider.TEMPORARILY_UNAVAILABLE};
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+                try {
+                    result[0] = service.getStatus(extras);
+                } catch (RemoteException e) {
+                    Log.w(TAG, e);
+                } catch (Exception e) {
+                    // never let remote service crash system server
+                    Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+                }
+            }
+        });
+        return result[0];
     }
 
     @Override
     public long getStatusUpdateTime() {
-        ILocationProvider service = getService();
-        if (service == null) return 0;
-
-        try {
-            return service.getStatusUpdateTime();
-        } catch (RemoteException e) {
-            Log.w(TAG, e);
-        } catch (Exception e) {
-            // never let remote service crash system server
-            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
-        }
-        return 0;
+        final long[] result = new long[] {0L};
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+                try {
+                    result[0] = service.getStatusUpdateTime();
+                } catch (RemoteException e) {
+                    Log.w(TAG, e);
+                } catch (Exception e) {
+                    // never let remote service crash system server
+                    Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+                }
+            }
+        });
+        return result[0];
     }
 
     @Override
     public boolean sendExtraCommand(String command, Bundle extras) {
-        ILocationProvider service = getService();
-        if (service == null) return false;
-
-        try {
-            return service.sendExtraCommand(command, extras);
-        } catch (RemoteException e) {
-            Log.w(TAG, e);
-        } catch (Exception e) {
-            // never let remote service crash system server
-            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
-        }
-        return false;
+        final boolean[] result = new boolean[] {false};
+        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
+            @Override
+            public void run(IBinder binder) {
+                ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
+                try {
+                    result[0] = service.sendExtraCommand(command, extras);
+                } catch (RemoteException e) {
+                    Log.w(TAG, e);
+                } catch (Exception e) {
+                    // never let remote service crash system server
+                    Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+                }
+            }
+        });
+        return result[0];
     }
  }
diff --git a/com/android/server/locksettings/SyntheticPasswordCrypto.java b/com/android/server/locksettings/SyntheticPasswordCrypto.java
index b7bca1f..ef94000 100644
--- a/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -112,7 +112,7 @@
         }
     }
 
-    public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
+    public static byte[] decryptBlobV1(String keyAlias, byte[] blob, byte[] applicationId) {
         try {
             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
             keyStore.load(null);
@@ -120,6 +120,20 @@
             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
             byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
             return decrypt(decryptionKey, intermediate);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException("Failed to decrypt blob", e);
+        }
+    }
+
+    public static byte[] decryptBlob(String keyAlias, byte[] blob, byte[] applicationId) {
+        try {
+            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+            keyStore.load(null);
+
+            SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
+            byte[] intermediate = decrypt(decryptionKey, blob);
+            return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
         } catch (CertificateException | IOException | BadPaddingException
                 | IllegalBlockSizeException
                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
@@ -150,9 +164,8 @@
             keyStore.setEntry(keyAlias,
                     new KeyStore.SecretKeyEntry(secretKey),
                     builder.build());
-            byte[] intermediate = encrypt(secretKey, data);
-            return encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
-
+            byte[] intermediate = encrypt(applicationId, APPLICATION_ID_PERSONALIZATION, data);
+            return encrypt(secretKey, intermediate);
         } catch (CertificateException | IOException | BadPaddingException
                 | IllegalBlockSizeException
                 | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException
diff --git a/com/android/server/locksettings/SyntheticPasswordManager.java b/com/android/server/locksettings/SyntheticPasswordManager.java
index 9440f17..1a1aa56 100644
--- a/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -101,7 +101,8 @@
     private static final byte WEAVER_VERSION = 1;
     private static final int INVALID_WEAVER_SLOT = -1;
 
-    private static final byte SYNTHETIC_PASSWORD_VERSION = 1;
+    private static final byte SYNTHETIC_PASSWORD_VERSION_V1 = 1;
+    private static final byte SYNTHETIC_PASSWORD_VERSION = 2;
     private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
     private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
 
@@ -389,11 +390,9 @@
     }
 
     public void removeUser(int userId) {
-        if (isWeaverAvailable()) {
-            for (long handle : mStorage.listSyntheticPasswordHandlesForUser(WEAVER_SLOT_NAME,
-                    userId)) {
-                destroyWeaverSlot(handle, userId);
-            }
+        for (long handle : mStorage.listSyntheticPasswordHandlesForUser(SP_BLOB_NAME, userId)) {
+            destroyWeaverSlot(handle, userId);
+            destroySPBlobKey(getHandleName(handle));
         }
     }
 
@@ -792,6 +791,7 @@
         byte[] pwdToken = computePasswordToken(credential, pwd);
 
         final byte[] applicationId;
+        final long sid;
         int weaverSlot = loadWeaverSlot(handle, userId);
         if (weaverSlot != INVALID_WEAVER_SLOT) {
             // Weaver based user password
@@ -804,6 +804,7 @@
             if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
                 return result;
             }
+            sid = GateKeeper.INVALID_SECURE_USER_ID;
             applicationId = transformUnderWeaverSecret(pwdToken, result.gkResponse.getPayload());
         } else {
             byte[] gkPwdToken = passwordTokenToGkInput(pwdToken);
@@ -836,12 +837,13 @@
                 result.gkResponse = VerifyCredentialResponse.ERROR;
                 return result;
             }
+            sid = sidFromPasswordHandle(pwd.passwordHandle);
             applicationId = transformUnderSecdiscardable(pwdToken,
                     loadSecdiscardable(handle, userId));
         }
 
         result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
-                applicationId, userId);
+                applicationId, sid, userId);
 
         // Perform verifyChallenge to refresh auth tokens for GK if user password exists.
         result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
@@ -877,7 +879,7 @@
         }
         byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
         result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
-                applicationId, userId);
+                applicationId, 0L, userId);
         if (result.authToken != null) {
             result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
             if (result.gkResponse == null) {
@@ -892,19 +894,26 @@
     }
 
     private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
-            byte[] applicationId, int userId) {
+            byte[] applicationId, long sid, int userId) {
         byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
         if (blob == null) {
             return null;
         }
-        if (blob[0] != SYNTHETIC_PASSWORD_VERSION) {
+        final byte version = blob[0];
+        if (version != SYNTHETIC_PASSWORD_VERSION && version != SYNTHETIC_PASSWORD_VERSION_V1) {
             throw new RuntimeException("Unknown blob version");
         }
         if (blob[1] != type) {
             throw new RuntimeException("Invalid blob type");
         }
-        byte[] secret = decryptSPBlob(getHandleName(handle),
+        final byte[] secret;
+        if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
+            secret = SyntheticPasswordCrypto.decryptBlobV1(getHandleName(handle),
+                    Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+        } else {
+            secret = decryptSPBlob(getHandleName(handle),
                 Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+        }
         if (secret == null) {
             Log.e(TAG, "Fail to decrypt SP for user " + userId);
             return null;
@@ -919,6 +928,10 @@
         } else {
             result.syntheticPassword = new String(secret);
         }
+        if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
+            Log.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
+            createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
+        }
         return result;
     }
 
diff --git a/com/android/server/media/AudioPlaybackMonitor.java b/com/android/server/media/AudioPlaybackMonitor.java
deleted file mode 100644
index 791ee82..0000000
--- a/com/android/server/media/AudioPlaybackMonitor.java
+++ /dev/null
@@ -1,289 +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 com.android.server.media;
-
-import android.content.Context;
-import android.media.AudioManager.AudioPlaybackCallback;
-import android.media.AudioPlaybackConfiguration;
-import android.media.IAudioService;
-import android.media.IPlaybackConfigDispatcher;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.IntArray;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Monitors changes in audio playback, and notify the newly started audio playback through the
- * {@link OnAudioPlaybackStartedListener} and the activeness change through the
- * {@link OnAudioPlaybackActiveStateListener}.
- */
-class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
-    private static boolean DEBUG = MediaSessionService.DEBUG;
-    private static String TAG = "AudioPlaybackMonitor";
-
-    private static AudioPlaybackMonitor sInstance;
-
-    /**
-     * Called when audio playback is started for a given UID.
-     */
-    interface OnAudioPlaybackStartedListener {
-        void onAudioPlaybackStarted(int uid);
-    }
-
-    /**
-     * Called when audio player state is changed.
-     */
-    interface OnAudioPlayerActiveStateChangedListener {
-        void onAudioPlayerActiveStateChanged(int uid, boolean active);
-    }
-
-    private final Object mLock = new Object();
-    private final Context mContext;
-    private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners
-            = new ArrayList<>();
-    private final List<OnAudioPlayerActiveStateChangedListener>
-            mAudioPlayerActiveStateChangedListeners = new ArrayList<>();
-    private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>();
-    private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
-
-    // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
-    // The UID whose audio playback becomes active at the last comes first.
-    // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
-    private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
-
-    static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) {
-        if (sInstance == null) {
-            sInstance = new AudioPlaybackMonitor(context, audioService);
-        }
-        return sInstance;
-    }
-
-    private AudioPlaybackMonitor(Context context, IAudioService audioService) {
-        mContext = context;
-        try {
-            audioService.registerPlaybackCallback(this);
-        } catch (RemoteException e) {
-            Log.wtf(TAG, "Failed to register playback callback", e);
-        }
-    }
-
-    /**
-     * Called when the {@link AudioPlaybackConfiguration} is updated.
-     * <p>If an app starts audio playback, the app's local media session will be the media button
-     * session. If the app has multiple media sessions, the playback active local session will be
-     * picked.
-     *
-     * @param configs List of the current audio playback configuration
-     */
-    @Override
-    public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
-            boolean flush) {
-        if (flush) {
-            Binder.flushPendingCommands();
-        }
-        final long token = Binder.clearCallingIdentity();
-        try {
-            List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
-            List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners;
-            List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners;
-            synchronized (mLock) {
-                // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids,
-                // and find newly activated audio playbacks.
-                mActiveAudioPlaybackClientUids.clear();
-                for (AudioPlaybackConfiguration config : configs) {
-                    // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
-                    // (i.e. playback from the SoundPool class which is only for sound effects)
-                    // playback.
-                    // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
-                    // specific audio/video players.
-                    if (!config.isActive() || config.getPlayerType()
-                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
-                        continue;
-                    }
-
-                    mActiveAudioPlaybackClientUids.add(config.getClientUid());
-                    Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId());
-                    if (!isActiveState(oldState)) {
-                        if (DEBUG) {
-                            Log.d(TAG, "Found a new active media playback. " +
-                                    AudioPlaybackConfiguration.toLogFriendlyString(config));
-                        }
-                        // New active audio playback.
-                        newActiveAudioPlaybackClientUids.add(config.getClientUid());
-                        int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
-                        if (index == 0) {
-                            // It's the lastly played music app already. Skip updating.
-                            continue;
-                        } else if (index > 0) {
-                            mSortedAudioPlaybackClientUids.remove(index);
-                        }
-                        mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
-                    }
-                }
-                audioPlayerActiveStateChangedListeners = new ArrayList<>(
-                        mAudioPlayerActiveStateChangedListeners);
-                audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners);
-            }
-            // Notify the change of audio playback states.
-            for (AudioPlaybackConfiguration config : configs) {
-                boolean wasActive = isActiveState(
-                        mAudioPlaybackStates.get(config.getPlayerInterfaceId()));
-                boolean isActive = config.isActive();
-                if (wasActive != isActive) {
-                    for (OnAudioPlayerActiveStateChangedListener listener
-                            : audioPlayerActiveStateChangedListeners) {
-                        listener.onAudioPlayerActiveStateChanged(config.getClientUid(),
-                                isActive);
-                    }
-                }
-            }
-            // Notify the start of audio playback
-            for (int uid : newActiveAudioPlaybackClientUids) {
-                for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) {
-                    listener.onAudioPlaybackStarted(uid);
-                }
-            }
-            mAudioPlaybackStates.clear();
-            for (AudioPlaybackConfiguration config : configs) {
-                mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState());
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * Registers OnAudioPlaybackStartedListener.
-     */
-    public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
-        synchronized (mLock) {
-            mAudioPlaybackStartedListeners.add(listener);
-        }
-    }
-
-    /**
-     * Unregisters OnAudioPlaybackStartedListener.
-     */
-    public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
-        synchronized (mLock) {
-            mAudioPlaybackStartedListeners.remove(listener);
-        }
-    }
-
-    /**
-     * Registers OnAudioPlayerActiveStateChangedListener.
-     */
-    public void registerOnAudioPlayerActiveStateChangedListener(
-            OnAudioPlayerActiveStateChangedListener listener) {
-        synchronized (mLock) {
-            mAudioPlayerActiveStateChangedListeners.add(listener);
-        }
-    }
-
-    /**
-     * Unregisters OnAudioPlayerActiveStateChangedListener.
-     */
-    public void unregisterOnAudioPlayerActiveStateChangedListener(
-            OnAudioPlayerActiveStateChangedListener listener) {
-        synchronized (mLock) {
-            mAudioPlayerActiveStateChangedListeners.remove(listener);
-        }
-    }
-
-    /**
-     * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
-     * audio/video) The UID whose audio playback becomes active at the last comes first.
-     */
-    public IntArray getSortedAudioPlaybackClientUids() {
-        IntArray sortedAudioPlaybackClientUids = new IntArray();
-        synchronized (mLock) {
-            sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
-        }
-        return sortedAudioPlaybackClientUids;
-    }
-
-    /**
-     * Returns if the audio playback is active for the uid.
-     */
-    public boolean isPlaybackActive(int uid) {
-        synchronized (mLock) {
-            return mActiveAudioPlaybackClientUids.contains(uid);
-        }
-    }
-
-    /**
-     * Cleans up the sorted list of audio playback client UIDs with given {@param
-     * mediaButtonSessionUid}.
-     * <p>UIDs whose audio playback started after the media button session's audio playback
-     * cannot be the lastly played media app. So they won't needed anymore.
-     *
-     * @param mediaButtonSessionUid UID of the media button session.
-     */
-    public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
-        synchronized (mLock) {
-            int userId = UserHandle.getUserId(mediaButtonSessionUid);
-            for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
-                if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
-                    break;
-                }
-                int uid = mSortedAudioPlaybackClientUids.get(i);
-                if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
-                    // Clean up unnecessary UIDs.
-                    // It doesn't need to be managed profile aware because it's just to prevent
-                    // the list from increasing indefinitely. The media button session updating
-                    // shouldn't be affected by cleaning up.
-                    mSortedAudioPlaybackClientUids.remove(i);
-                }
-            }
-        }
-    }
-
-    /**
-     * Dumps {@link AudioPlaybackMonitor}.
-     */
-    public void dump(PrintWriter pw, String prefix) {
-        synchronized (mLock) {
-            pw.println(prefix + "Audio playback (lastly played comes first)");
-            String indent = prefix + "  ";
-            for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
-                int uid = mSortedAudioPlaybackClientUids.get(i);
-                pw.print(indent + "uid=" + uid + " packages=");
-                String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
-                if (packages != null && packages.length > 0) {
-                    for (int j = 0; j < packages.length; j++) {
-                        pw.print(packages[j] + " ");
-                    }
-                }
-                pw.println();
-            }
-        }
-    }
-
-    private boolean isActiveState(Integer state) {
-        return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
-    }
-}
diff --git a/com/android/server/media/AudioPlayerStateMonitor.java b/com/android/server/media/AudioPlayerStateMonitor.java
new file mode 100644
index 0000000..be223f1
--- /dev/null
+++ b/com/android/server/media/AudioPlayerStateMonitor.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioPlaybackConfiguration;
+import android.media.IAudioService;
+import android.media.IPlaybackConfigDispatcher;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Monitors the state changes of audio players.
+ */
+class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
+    private static boolean DEBUG = MediaSessionService.DEBUG;
+    private static String TAG = "AudioPlayerStateMonitor";
+
+    private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
+
+    /**
+     * Called when the state of audio player is changed.
+     */
+    interface OnAudioPlayerStateChangedListener {
+        void onAudioPlayerStateChanged(
+                int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+    }
+
+    private final static class MessageHandler extends Handler {
+        private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+
+        private final OnAudioPlayerStateChangedListener mListsner;
+
+        public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+            super(looper);
+            mListsner = listener;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_AUDIO_PLAYER_STATE_CHANGED:
+                    mListsner.onAudioPlayerStateChanged(
+                            msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+                    break;
+            }
+        }
+
+        public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
+                AudioPlaybackConfiguration config) {
+            obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+        }
+    }
+
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
+            new HashMap<>();
+    @GuardedBy("mLock")
+    private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+    @GuardedBy("mLock")
+    private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+    // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
+    // The UID whose audio playback becomes active at the last comes first.
+    // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
+    @GuardedBy("mLock")
+    private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
+
+    @GuardedBy("mLock")
+    private boolean mRegisteredToAudioService;
+
+    static AudioPlayerStateMonitor getInstance() {
+        return sInstance;
+    }
+
+    private AudioPlayerStateMonitor() {
+    }
+
+    /**
+     * Called when the {@link AudioPlaybackConfiguration} is updated.
+     * <p>If an app starts audio playback, the app's local media session will be the media button
+     * session. If the app has multiple media sessions, the playback active local session will be
+     * picked.
+     *
+     * @param configs List of the current audio playback configuration
+     */
+    @Override
+    public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
+            boolean flush) {
+        if (flush) {
+            Binder.flushPendingCommands();
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
+            final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
+                    new HashMap<>(mAudioPlayersForUid);
+            synchronized (mLock) {
+                mAudioPlayerStates.clear();
+                mAudioPlayersForUid.clear();
+                for (AudioPlaybackConfiguration config : configs) {
+                    int pii = config.getPlayerInterfaceId();
+                    int uid = config.getClientUid();
+                    mAudioPlayerStates.put(pii, config.getPlayerState());
+                    HashSet<Integer> players = mAudioPlayersForUid.get(uid);
+                    if (players == null) {
+                        players = new HashSet<Integer>();
+                        players.add(pii);
+                        mAudioPlayersForUid.put(uid, players);
+                    } else {
+                        players.add(pii);
+                    }
+                }
+                for (AudioPlaybackConfiguration config : configs) {
+                    if (!config.isActive()) {
+                        continue;
+                    }
+
+                    int uid = config.getClientUid();
+                    if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Found a new active media playback. " +
+                                    AudioPlaybackConfiguration.toLogFriendlyString(config));
+                        }
+                        // New active audio playback.
+                        int index = mSortedAudioPlaybackClientUids.indexOf(uid);
+                        if (index == 0) {
+                            // It's the lastly played music app already. Skip updating.
+                            continue;
+                        } else if (index > 0) {
+                            mSortedAudioPlaybackClientUids.remove(index);
+                        }
+                        mSortedAudioPlaybackClientUids.add(0, uid);
+                    }
+                }
+                // Notify the change of audio player states.
+                for (AudioPlaybackConfiguration config : configs) {
+                    final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
+                    final int prevStateInt =
+                            (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
+                                prevState.intValue();
+                    if (prevStateInt != config.getPlayerState()) {
+                        sendAudioPlayerStateChangedMessageLocked(
+                                config.getClientUid(), prevStateInt, config);
+                    }
+                }
+                for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
+                    // If all players for prevUid is removed, notify the prev state was
+                    // PLAYER_STATE_STARTED only when there were a player whose state was
+                    // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
+                    if (!mAudioPlayersForUid.containsKey(prevUid)) {
+                        Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
+                        int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
+                        for (int pii : prevPlayers) {
+                            Integer state = prevAudioPlayerStates.get(pii);
+                            if (state == null) {
+                                continue;
+                            }
+                            if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                                prevState = state;
+                                break;
+                            } else if (prevState
+                                    == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
+                                prevState = state;
+                            }
+                        }
+                        sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Registers OnAudioPlayerStateChangedListener.
+     */
+    public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+        synchronized (mLock) {
+            mListenerMap.put(listener, new MessageHandler((handler == null) ?
+                    Looper.myLooper() : handler.getLooper(), listener));
+        }
+    }
+
+    /**
+     * Unregisters OnAudioPlayerStateChangedListener.
+     */
+    public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+        synchronized (mLock) {
+            mListenerMap.remove(listener);
+        }
+    }
+
+    /**
+     * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
+     * audio/video) The UID whose audio playback becomes active at the last comes first.
+     */
+    public IntArray getSortedAudioPlaybackClientUids() {
+        IntArray sortedAudioPlaybackClientUids = new IntArray();
+        synchronized (mLock) {
+            sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
+        }
+        return sortedAudioPlaybackClientUids;
+    }
+
+    /**
+     * Returns if the audio playback is active for the uid.
+     */
+    public boolean isPlaybackActive(int uid) {
+        synchronized (mLock) {
+            Set<Integer> players = mAudioPlayersForUid.get(uid);
+            if (players == null) {
+                return false;
+            }
+            for (Integer pii : players) {
+                if (isActiveState(mAudioPlayerStates.get(pii))) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Cleans up the sorted list of audio playback client UIDs with given {@param
+     * mediaButtonSessionUid}.
+     * <p>UIDs whose audio playback are inactive and have started before the media button session's
+     * audio playback cannot be the lastly played media app. So they won't needed anymore.
+     *
+     * @param mediaButtonSessionUid UID of the media button session.
+     */
+    public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
+        synchronized (mLock) {
+            int userId = UserHandle.getUserId(mediaButtonSessionUid);
+            for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
+                if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
+                    break;
+                }
+                int uid = mSortedAudioPlaybackClientUids.get(i);
+                if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
+                    // Clean up unnecessary UIDs.
+                    // It doesn't need to be managed profile aware because it's just to prevent
+                    // the list from increasing indefinitely. The media button session updating
+                    // shouldn't be affected by cleaning up.
+                    mSortedAudioPlaybackClientUids.remove(i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Dumps {@link AudioPlayerStateMonitor}.
+     */
+    public void dump(Context context, PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.println(prefix + "Audio playback (lastly played comes first)");
+            String indent = prefix + "  ";
+            for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
+                int uid = mSortedAudioPlaybackClientUids.get(i);
+                pw.print(indent + "uid=" + uid + " packages=");
+                String[] packages = context.getPackageManager().getPackagesForUid(uid);
+                if (packages != null && packages.length > 0) {
+                    for (int j = 0; j < packages.length; j++) {
+                        pw.print(packages[j] + " ");
+                    }
+                }
+                pw.println();
+            }
+        }
+    }
+
+    public void registerSelfIntoAudioServiceIfNeeded(IAudioService audioService) {
+        synchronized (mLock) {
+            try {
+                if (!mRegisteredToAudioService) {
+                    audioService.registerPlaybackCallback(this);
+                    mRegisteredToAudioService = true;
+                }
+            } catch (RemoteException e) {
+                Log.wtf(TAG, "Failed to register playback callback", e);
+                mRegisteredToAudioService = false;
+            }
+        }
+    }
+
+    private void sendAudioPlayerStateChangedMessageLocked(
+            final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+        for (MessageHandler messageHandler : mListenerMap.values()) {
+            messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+        }
+    }
+
+    private static boolean isActiveState(Integer state) {
+        return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
+    }
+}
diff --git a/com/android/server/media/MediaRouterService.java b/com/android/server/media/MediaRouterService.java
index 1cfd5f0..3c9e1d4 100644
--- a/com/android/server/media/MediaRouterService.java
+++ b/com/android/server/media/MediaRouterService.java
@@ -19,12 +19,14 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.Watchdog;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
@@ -96,7 +98,8 @@
     private int mCurrentUserId = -1;
     private boolean mGlobalBluetoothA2dpOn = false;
     private final IAudioService mAudioService;
-    private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
+    private final Handler mHandler = new Handler();
     private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
 
     public MediaRouterService(Context context) {
@@ -106,31 +109,57 @@
         mAudioService = IAudioService.Stub.asInterface(
                 ServiceManager.getService(Context.AUDIO_SERVICE));
 
-        mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(context, mAudioService);
-        mAudioPlaybackMonitor.registerOnAudioPlayerActiveStateChangedListener(
-                new AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener() {
+        mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+        mAudioPlayerStateMonitor.registerListener(
+                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+            static final long WAIT_MS = 500;
+            final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    restoreBluetoothA2dp();
+                }
+            };
+
             @Override
-            public void onAudioPlayerActiveStateChanged(int uid, boolean active) {
+            public void onAudioPlayerStateChanged(
+                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+                int restoreUid = -1;
+                boolean active = config == null ? false : config.isActive();
                 if (active) {
-                    restoreRoute(uid);
+                    restoreUid = uid;
+                } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    // Noting to do if the prev state is not an active state.
+                    return;
                 } else {
                     IntArray sortedAudioPlaybackClientUids =
-                            mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
-                    boolean restored = false;
-                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); i++) {
-                        if (mAudioPlaybackMonitor.isPlaybackActive(
+                            mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
+                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
+                        if (mAudioPlayerStateMonitor.isPlaybackActive(
                                 sortedAudioPlaybackClientUids.get(i))) {
-                            restoreRoute(sortedAudioPlaybackClientUids.get(i));
-                            restored = true;
+                            restoreUid = sortedAudioPlaybackClientUids.get(i);
                             break;
                         }
                     }
-                    if (!restored) {
-                        restoreBluetoothA2dp();
+                }
+
+                mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
+                if (restoreUid >= 0) {
+                    restoreRoute(restoreUid);
+                    if (DEBUG) {
+                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+                                + " active " + active + " restoring " + restoreUid);
+                    }
+                } else {
+                    mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
+                    if (DEBUG) {
+                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+                                + " active " + active + " delaying");
                     }
                 }
             }
-        });
+        }, mHandler);
+        mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
+
         AudioRoutesInfo audioRoutes = null;
         try {
             audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
@@ -261,9 +290,14 @@
 
         final long token = Binder.clearCallingIdentity();
         try {
+            ClientRecord clientRecord;
             synchronized (mLock) {
-                return isPlaybackActiveLocked(client);
+                clientRecord = mAllClientRecords.get(client.asBinder());
             }
+            if (clientRecord != null) {
+                return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
+            }
+            return false;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -480,14 +514,6 @@
         return null;
     }
 
-    private boolean isPlaybackActiveLocked(IMediaRouterClient client) {
-        ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
-        if (clientRecord != null) {
-            return mAudioPlaybackMonitor.isPlaybackActive(clientRecord.mUid);
-        }
-        return false;
-    }
-
     private void setDiscoveryRequestLocked(IMediaRouterClient client,
             int routeTypes, boolean activeScan) {
         final IBinder binder = client.asBinder();
diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index aa65244..f6a81d0 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
@@ -31,7 +32,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.media.AudioManager;
-import android.media.AudioManagerInternal;
+import android.media.AudioPlaybackConfiguration;
 import android.media.AudioSystem;
 import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
@@ -68,7 +69,6 @@
 import android.view.ViewConfiguration;
 
 import com.android.internal.util.DumpUtils;
-import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.Watchdog;
 import com.android.server.Watchdog.Monitor;
@@ -104,7 +104,6 @@
 
     private KeyguardManager mKeyguardManager;
     private IAudioService mAudioService;
-    private AudioManagerInternal mAudioManagerInternal;
     private ContentResolver mContentResolver;
     private SettingsObserver mSettingsObserver;
     private INotificationManager mNotificationManager;
@@ -114,7 +113,7 @@
     // It's always not null after the MediaSessionService is started.
     private FullUserRecord mCurrentFullUserRecord;
     private MediaSessionRecord mGlobalPrioritySession;
-    private AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
 
     // Used to notify system UI when remote volume was changed. TODO find a
     // better way to handle this.
@@ -137,11 +136,16 @@
         mKeyguardManager =
                 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
         mAudioService = getAudioService();
-        mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(getContext(), mAudioService);
-        mAudioPlaybackMonitor.registerOnAudioPlaybackStartedListener(
-                new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
+        mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+        mAudioPlayerStateMonitor.registerListener(
+                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
             @Override
-            public void onAudioPlaybackStarted(int uid) {
+            public void onAudioPlayerStateChanged(
+                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+                if (config == null || !config.isActive() || config.getPlayerType()
+                        == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+                    return;
+                }
                 synchronized (mLock) {
                     FullUserRecord user =
                             getFullUserRecordLocked(UserHandle.getUserId(uid));
@@ -150,8 +154,8 @@
                     }
                 }
             }
-        });
-        mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+        }, null /* handler */);
+        mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
         mContentResolver = getContext().getContentResolver();
         mSettingsObserver = new SettingsObserver();
         mSettingsObserver.observe();
@@ -650,7 +654,7 @@
 
         public FullUserRecord(int fullUserId) {
             mFullUserId = fullUserId;
-            mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
+            mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
             // Restore the remembered media button receiver before the boot.
             String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
                     Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
@@ -1309,7 +1313,7 @@
                 for (int i = 0; i < count; i++) {
                     mUserRecords.valueAt(i).dumpLocked(pw, "");
                 }
-                mAudioPlaybackMonitor.dump(pw, "");
+                mAudioPlayerStateMonitor.dump(getContext(), pw, "");
             }
         }
 
diff --git a/com/android/server/media/MediaSessionStack.java b/com/android/server/media/MediaSessionStack.java
index d9fe72e..719ec36 100644
--- a/com/android/server/media/MediaSessionStack.java
+++ b/com/android/server/media/MediaSessionStack.java
@@ -75,7 +75,7 @@
      */
     private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
 
-    private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+    private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
 
     /**
@@ -84,7 +84,6 @@
      */
     private MediaSessionRecord mMediaButtonSession;
 
-    private MediaSessionRecord mCachedDefault;
     private MediaSessionRecord mCachedVolumeDefault;
 
     /**
@@ -93,8 +92,8 @@
     private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
             new SparseArray<>();
 
-    MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) {
-        mAudioPlaybackMonitor = monitor;
+    MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
+        mAudioPlayerStateMonitor = monitor;
         mOnMediaButtonSessionChangedListener = listener;
     }
 
@@ -187,13 +186,13 @@
         if (DEBUG) {
             Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
         }
-        IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+        IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
         for (int i = 0; i < audioPlaybackUids.size(); i++) {
             MediaSessionRecord mediaButtonSession =
                     findMediaButtonSession(audioPlaybackUids.get(i));
             if (mediaButtonSession != null) {
                 // Found the media button session.
-                mAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
+                mAudioPlayerStateMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
                 if (mMediaButtonSession != mediaButtonSession) {
                     updateMediaButtonSession(mediaButtonSession);
                 }
@@ -216,7 +215,7 @@
         for (MediaSessionRecord session : mSessions) {
             if (uid == session.getUid()) {
                 if (session.getPlaybackState() != null && session.isPlaybackActive() ==
-                        mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
+                        mAudioPlayerStateMonitor.isPlaybackActive(session.getUid())) {
                     // If there's a media session whose PlaybackState matches
                     // the audio playback state, return it immediately.
                     return session;
@@ -376,7 +375,6 @@
     }
 
     private void clearCache(int userId) {
-        mCachedDefault = null;
         mCachedVolumeDefault = null;
         mCachedActiveLists.remove(userId);
         // mCachedActiveLists may also include the list of sessions for UserHandle.USER_ALL,
diff --git a/com/android/server/net/NetworkPolicyManagerService.java b/com/android/server/net/NetworkPolicyManagerService.java
index b4056b3..3fa3cd4 100644
--- a/com/android/server/net/NetworkPolicyManagerService.java
+++ b/com/android/server/net/NetworkPolicyManagerService.java
@@ -142,6 +142,7 @@
 import android.os.MessageQueue.IdleHandler;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.Process;
@@ -192,7 +193,6 @@
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemConfig;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
 
 import libcore.io.IoUtils;
 
@@ -3746,7 +3746,7 @@
             extends UsageStatsManagerInternal.AppIdleStateChangeListener {
 
         @Override
-        public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
+        public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) {
             try {
                 final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
diff --git a/com/android/server/net/watchlist/DigestUtils.java b/com/android/server/net/watchlist/DigestUtils.java
new file mode 100644
index 0000000..57becb0
--- /dev/null
+++ b/com/android/server/net/watchlist/DigestUtils.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 com.android.server.net.watchlist;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Utils for calculating digests.
+ */
+public class DigestUtils {
+
+    private static final int FILE_READ_BUFFER_SIZE = 16 * 1024;
+
+    private DigestUtils() {}
+
+    /** @return SHA256 hash of the provided file */
+    public static byte[] getSha256Hash(File apkFile) throws IOException, NoSuchAlgorithmException {
+        try (InputStream stream = new FileInputStream(apkFile)) {
+            return getSha256Hash(stream);
+        }
+    }
+
+    /** @return SHA256 hash of data read from the provided input stream */
+    public static byte[] getSha256Hash(InputStream stream)
+            throws IOException, NoSuchAlgorithmException {
+        MessageDigest digester = MessageDigest.getInstance("SHA256");
+
+        int bytesRead;
+        byte[] buf = new byte[FILE_READ_BUFFER_SIZE];
+        while ((bytesRead = stream.read(buf)) >= 0) {
+            digester.update(buf, 0, bytesRead);
+        }
+        return digester.digest();
+    }
+}
\ No newline at end of file
diff --git a/com/android/server/net/watchlist/HarmfulDigests.java b/com/android/server/net/watchlist/HarmfulDigests.java
new file mode 100644
index 0000000..27c22ce
--- /dev/null
+++ b/com/android/server/net/watchlist/HarmfulDigests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import com.android.internal.util.HexDump;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class to store all harmful digests in memory.
+ * TODO: Optimize memory usage using byte array with binary search.
+ */
+class HarmfulDigests {
+
+    private final Set<String> mDigestSet;
+
+    HarmfulDigests(List<byte[]> digests) {
+        final HashSet<String> tmpDigestSet = new HashSet<>();
+        final int size = digests.size();
+        for (int i = 0; i < size; i++) {
+            tmpDigestSet.add(HexDump.toHexString(digests.get(i)));
+        }
+        mDigestSet = Collections.unmodifiableSet(tmpDigestSet);
+    }
+
+    public boolean contains(byte[] digest) {
+        return mDigestSet.contains(HexDump.toHexString(digest));
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        for (String digest : mDigestSet) {
+            pw.println(digest);
+        }
+        pw.println("");
+    }
+}
diff --git a/com/android/server/net/watchlist/NetworkWatchlistService.java b/com/android/server/net/watchlist/NetworkWatchlistService.java
new file mode 100644
index 0000000..171703a
--- /dev/null
+++ b/com/android/server/net/watchlist/NetworkWatchlistService.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.net.NetworkWatchlistManager;
+import android.net.metrics.IpConnectivityLog;
+import android.os.Binder;
+import android.os.Process;
+import android.os.SharedMemory;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.server.ServiceThread;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Implementation of network watchlist service.
+ */
+public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
+
+    private static final String TAG = NetworkWatchlistService.class.getSimpleName();
+    static final boolean DEBUG = false;
+
+    private static final String PROPERTY_NETWORK_WATCHLIST_ENABLED =
+            "ro.network_watchlist_enabled";
+
+    private static final int MAX_NUM_OF_WATCHLIST_DIGESTS = 10000;
+
+    public static class Lifecycle extends SystemService {
+        private NetworkWatchlistService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
+                // Watchlist service is disabled
+                return;
+            }
+            mService = new NetworkWatchlistService(getContext());
+            publishBinderService(Context.NETWORK_WATCHLIST_SERVICE, mService);
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
+                // Watchlist service is disabled
+                return;
+            }
+            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+                try {
+                    mService.initIpConnectivityMetrics();
+                    mService.startWatchlistLogging();
+                } catch (RemoteException e) {
+                    // Should not happen
+                }
+                ReportWatchlistJobService.schedule(getContext());
+            }
+        }
+    }
+
+    private volatile boolean mIsLoggingEnabled = false;
+    private final Object mLoggingSwitchLock = new Object();
+
+    private final WatchlistSettings mSettings;
+    private final Context mContext;
+
+    // Separate thread to handle expensive watchlist logging work.
+    private final ServiceThread mHandlerThread;
+
+    @VisibleForTesting
+    IIpConnectivityMetrics mIpConnectivityMetrics;
+    @VisibleForTesting
+    WatchlistLoggingHandler mNetworkWatchlistHandler;
+
+    public NetworkWatchlistService(Context context) {
+        mContext = context;
+        mSettings = WatchlistSettings.getInstance();
+        mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+                        /* allowIo */ false);
+        mHandlerThread.start();
+        mNetworkWatchlistHandler = new WatchlistLoggingHandler(mContext,
+                mHandlerThread.getLooper());
+        mNetworkWatchlistHandler.reportWatchlistIfNecessary();
+    }
+
+    // For testing only
+    @VisibleForTesting
+    NetworkWatchlistService(Context context, ServiceThread handlerThread,
+            WatchlistLoggingHandler handler, IIpConnectivityMetrics ipConnectivityMetrics) {
+        mContext = context;
+        mSettings = WatchlistSettings.getInstance();
+        mHandlerThread = handlerThread;
+        mNetworkWatchlistHandler = handler;
+        mIpConnectivityMetrics = ipConnectivityMetrics;
+    }
+
+    private void initIpConnectivityMetrics() {
+        mIpConnectivityMetrics = (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface(
+                ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
+    }
+
+    private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() {
+        @Override
+        public void onDnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
+                long timestamp, int uid) {
+            if (!mIsLoggingEnabled) {
+                return;
+            }
+            mNetworkWatchlistHandler.asyncNetworkEvent(hostname, ipAddresses, uid);
+        }
+
+        @Override
+        public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) {
+            if (!mIsLoggingEnabled) {
+                return;
+            }
+            mNetworkWatchlistHandler.asyncNetworkEvent(null, new String[]{ipAddr}, uid);
+        }
+    };
+
+    @VisibleForTesting
+    protected boolean startWatchlistLoggingImpl() throws RemoteException {
+        if (DEBUG) {
+            Slog.i(TAG, "Starting watchlist logging.");
+        }
+        synchronized (mLoggingSwitchLock) {
+            if (mIsLoggingEnabled) {
+                Slog.w(TAG, "Watchlist logging is already running");
+                return true;
+            }
+            try {
+                if (mIpConnectivityMetrics.addNetdEventCallback(
+                        INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST, mNetdEventCallback)) {
+                    mIsLoggingEnabled = true;
+                    return true;
+                } else {
+                    return false;
+                }
+            } catch (RemoteException re) {
+                // Should not happen
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public boolean startWatchlistLogging() throws RemoteException {
+        enforceWatchlistLoggingPermission();
+        return startWatchlistLoggingImpl();
+    }
+
+    @VisibleForTesting
+    protected boolean stopWatchlistLoggingImpl() {
+        if (DEBUG) {
+            Slog.i(TAG, "Stopping watchlist logging");
+        }
+        synchronized (mLoggingSwitchLock) {
+            if (!mIsLoggingEnabled) {
+                Slog.w(TAG, "Watchlist logging is not running");
+                return true;
+            }
+            // stop the logging regardless of whether we fail to unregister listener
+            mIsLoggingEnabled = false;
+
+            try {
+                return mIpConnectivityMetrics.removeNetdEventCallback(
+                        INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST);
+            } catch (RemoteException re) {
+                // Should not happen
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public boolean stopWatchlistLogging() throws RemoteException {
+        enforceWatchlistLoggingPermission();
+        return stopWatchlistLoggingImpl();
+    }
+
+    private void enforceWatchlistLoggingPermission() {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID) {
+            throw new SecurityException(String.format("Uid %d has no permission to change watchlist"
+                    + " setting.", uid));
+        }
+    }
+
+    /**
+     * Set a new network watchlist.
+     * This method should be called by ConfigUpdater only.
+     *
+     * @return True if network watchlist is updated.
+     */
+    public boolean setNetworkSecurityWatchlist(List<byte[]> domainsCrc32Digests,
+            List<byte[]> domainsSha256Digests,
+            List<byte[]> ipAddressesCrc32Digests,
+            List<byte[]> ipAddressesSha256Digests) {
+        Slog.i(TAG, "Setting network watchlist");
+        if (domainsCrc32Digests == null || domainsSha256Digests == null
+                || ipAddressesCrc32Digests == null || ipAddressesSha256Digests == null) {
+            Slog.e(TAG, "Parameters cannot be null");
+            return false;
+        }
+        if (domainsCrc32Digests.size() != domainsSha256Digests.size()
+                || ipAddressesCrc32Digests.size() != ipAddressesSha256Digests.size()) {
+            Slog.e(TAG, "Must need to have the same number of CRC32 and SHA256 digests");
+            return false;
+        }
+        if (domainsSha256Digests.size() + ipAddressesSha256Digests.size()
+                > MAX_NUM_OF_WATCHLIST_DIGESTS) {
+            Slog.e(TAG, "Total watchlist size cannot exceed " + MAX_NUM_OF_WATCHLIST_DIGESTS);
+            return false;
+        }
+        mSettings.writeSettingsToDisk(domainsCrc32Digests, domainsSha256Digests,
+                ipAddressesCrc32Digests, ipAddressesSha256Digests);
+        Slog.i(TAG, "Set network watchlist: Success");
+        return true;
+    }
+
+    @Override
+    public void reportWatchlistIfNecessary() {
+        // Allow any apps to trigger report event, as we won't run it if it's too early.
+        mNetworkWatchlistHandler.reportWatchlistIfNecessary();
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        mSettings.dump(fd, pw, args);
+    }
+
+}
diff --git a/com/android/server/net/watchlist/ReportWatchlistJobService.java b/com/android/server/net/watchlist/ReportWatchlistJobService.java
new file mode 100644
index 0000000..dfeb1b2
--- /dev/null
+++ b/com/android/server/net/watchlist/ReportWatchlistJobService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.NetworkWatchlistManager;
+import android.util.Slog;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A job that periodically report watchlist records.
+ */
+public class ReportWatchlistJobService extends JobService {
+
+    private static final boolean DEBUG = NetworkWatchlistService.DEBUG;
+    private static final String TAG = "WatchlistJobService";
+
+    // Unique job id used in system service, other jobs should not use the same value.
+    public static final int REPORT_WATCHLIST_RECORDS_JOB_ID = 0xd7689;
+    public static final long REPORT_WATCHLIST_RECORDS_PERIOD_MILLIS =
+            TimeUnit.HOURS.toMillis(12);
+
+    @Override
+    public boolean onStartJob(final JobParameters jobParameters) {
+        if (jobParameters.getJobId() != REPORT_WATCHLIST_RECORDS_JOB_ID) {
+            return false;
+        }
+        if (DEBUG) Slog.d(TAG, "Start scheduled job.");
+        new NetworkWatchlistManager(this).reportWatchlistIfNecessary();
+        jobFinished(jobParameters, false);
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters jobParameters) {
+        return true; // Reschedule when possible.
+    }
+
+    /**
+     * Schedule the {@link ReportWatchlistJobService} to run periodically.
+     */
+    public static void schedule(Context context) {
+        if (DEBUG) Slog.d(TAG, "Scheduling records aggregator task");
+        final JobScheduler scheduler =
+                (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        scheduler.schedule(new JobInfo.Builder(REPORT_WATCHLIST_RECORDS_JOB_ID,
+                new ComponentName(context, ReportWatchlistJobService.class))
+                //.setOverrideDeadline(45 * 1000) // Schedule job soon, for testing.
+                .setPeriodic(REPORT_WATCHLIST_RECORDS_PERIOD_MILLIS)
+                .setRequiresDeviceIdle(true)
+                .setRequiresBatteryNotLow(true)
+                .setPersisted(false)
+                .build());
+    }
+
+}
diff --git a/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/com/android/server/net/watchlist/WatchlistLoggingHandler.java
new file mode 100644
index 0000000..2247558
--- /dev/null
+++ b/com/android/server/net/watchlist/WatchlistLoggingHandler.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.DropBoxManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Handler class for network watchlist logging on a background thread.
+ */
+class WatchlistLoggingHandler extends Handler {
+
+    private static final String TAG = WatchlistLoggingHandler.class.getSimpleName();
+    private static final boolean DEBUG = NetworkWatchlistService.DEBUG;
+
+    @VisibleForTesting
+    static final int LOG_WATCHLIST_EVENT_MSG = 1;
+    @VisibleForTesting
+    static final int REPORT_RECORDS_IF_NECESSARY_MSG = 2;
+
+    private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
+    private static final String DROPBOX_TAG = "network_watchlist_report";
+
+    private final Context mContext;
+    private final ContentResolver mResolver;
+    private final PackageManager mPm;
+    private final WatchlistReportDbHelper mDbHelper;
+    private final WatchlistSettings mSettings;
+    // A cache for uid and apk digest mapping.
+    // As uid won't be reused until reboot, it's safe to assume uid is unique per signature and app.
+    // TODO: Use more efficient data structure.
+    private final HashMap<Integer, byte[]> mCachedUidDigestMap = new HashMap<>();
+
+    private interface WatchlistEventKeys {
+        String HOST = "host";
+        String IP_ADDRESSES = "ipAddresses";
+        String UID = "uid";
+        String TIMESTAMP = "timestamp";
+    }
+
+    WatchlistLoggingHandler(Context context, Looper looper) {
+        super(looper);
+        mContext = context;
+        mPm = mContext.getPackageManager();
+        mResolver = mContext.getContentResolver();
+        mDbHelper = WatchlistReportDbHelper.getInstance(context);
+        mSettings = WatchlistSettings.getInstance();
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case LOG_WATCHLIST_EVENT_MSG: {
+                final Bundle data = msg.getData();
+                handleNetworkEvent(
+                        data.getString(WatchlistEventKeys.HOST),
+                        data.getStringArray(WatchlistEventKeys.IP_ADDRESSES),
+                        data.getInt(WatchlistEventKeys.UID),
+                        data.getLong(WatchlistEventKeys.TIMESTAMP)
+                );
+                break;
+            }
+            case REPORT_RECORDS_IF_NECESSARY_MSG:
+                tryAggregateRecords();
+                break;
+            default: {
+                Slog.d(TAG, "WatchlistLoggingHandler received an unknown of message.");
+                break;
+            }
+        }
+    }
+
+    /**
+     * Report network watchlist records if we collected enough data.
+     */
+    public void reportWatchlistIfNecessary() {
+        final Message msg = obtainMessage(REPORT_RECORDS_IF_NECESSARY_MSG);
+        sendMessage(msg);
+    }
+
+    /**
+     * Insert network traffic event to watchlist async queue processor.
+     */
+    public void asyncNetworkEvent(String host, String[] ipAddresses, int uid) {
+        final Message msg = obtainMessage(LOG_WATCHLIST_EVENT_MSG);
+        final Bundle bundle = new Bundle();
+        bundle.putString(WatchlistEventKeys.HOST, host);
+        bundle.putStringArray(WatchlistEventKeys.IP_ADDRESSES, ipAddresses);
+        bundle.putInt(WatchlistEventKeys.UID, uid);
+        bundle.putLong(WatchlistEventKeys.TIMESTAMP, System.currentTimeMillis());
+        msg.setData(bundle);
+        sendMessage(msg);
+    }
+
+    private void handleNetworkEvent(String hostname, String[] ipAddresses,
+            int uid, long timestamp) {
+        if (DEBUG) {
+            Slog.i(TAG, "handleNetworkEvent with host: " + hostname + ", uid: " + uid);
+        }
+        final String cncDomain = searchAllSubDomainsInWatchlist(hostname);
+        if (cncDomain != null) {
+            insertRecord(getDigestFromUid(uid), cncDomain, timestamp);
+        } else {
+            final String cncIp = searchIpInWatchlist(ipAddresses);
+            if (cncIp != null) {
+                insertRecord(getDigestFromUid(uid), cncIp, timestamp);
+            }
+        }
+    }
+
+    private boolean insertRecord(byte[] digest, String cncHost, long timestamp) {
+        final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp);
+        tryAggregateRecords();
+        return result;
+    }
+
+    private boolean shouldReportNetworkWatchlist() {
+        final long lastReportTime = Settings.Global.getLong(mResolver,
+                Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, 0L);
+        final long currentTimestamp = System.currentTimeMillis();
+        if (currentTimestamp < lastReportTime) {
+            Slog.i(TAG, "Last report time is larger than current time, reset report");
+            mDbHelper.cleanup();
+            return false;
+        }
+        return currentTimestamp >= lastReportTime + ONE_DAY_MS;
+    }
+
+    private void tryAggregateRecords() {
+        if (shouldReportNetworkWatchlist()) {
+            Slog.i(TAG, "Start aggregating watchlist records.");
+            final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+            if (dbox != null && !dbox.isTagEnabled(DROPBOX_TAG)) {
+                final WatchlistReportDbHelper.AggregatedResult aggregatedResult =
+                        mDbHelper.getAggregatedRecords();
+                final byte[] encodedResult = encodeAggregatedResult(aggregatedResult);
+                if (encodedResult != null) {
+                    addEncodedReportToDropBox(encodedResult);
+                }
+            }
+            mDbHelper.cleanup();
+            Settings.Global.putLong(mResolver, Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
+                    System.currentTimeMillis());
+        } else {
+            Slog.i(TAG, "No need to aggregate record yet.");
+        }
+    }
+
+    private byte[] encodeAggregatedResult(
+            WatchlistReportDbHelper.AggregatedResult aggregatedResult) {
+        // TODO: Encode results using differential privacy.
+        return null;
+    }
+
+    private void addEncodedReportToDropBox(byte[] encodedReport) {
+        final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+        dbox.addData(DROPBOX_TAG, encodedReport, 0);
+    }
+
+    /**
+     * Get app digest from app uid.
+     */
+    private byte[] getDigestFromUid(int uid) {
+        final byte[] cachedDigest = mCachedUidDigestMap.get(uid);
+        if (cachedDigest != null) {
+            return cachedDigest;
+        }
+        final String[] packageNames = mPm.getPackagesForUid(uid);
+        final int userId = UserHandle.getUserId(uid);
+        if (!ArrayUtils.isEmpty(packageNames)) {
+            for (String packageName : packageNames) {
+                try {
+                    final String apkPath = mPm.getPackageInfoAsUser(packageName,
+                            PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                    | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId)
+                            .applicationInfo.publicSourceDir;
+                    if (TextUtils.isEmpty(apkPath)) {
+                        Slog.w(TAG, "Cannot find apkPath for " + packageName);
+                        continue;
+                    }
+                    final byte[] digest = DigestUtils.getSha256Hash(new File(apkPath));
+                    mCachedUidDigestMap.put(uid, digest);
+                    return digest;
+                } catch (NameNotFoundException | NoSuchAlgorithmException | IOException e) {
+                    Slog.e(TAG, "Should not happen", e);
+                    return null;
+                }
+            }
+        } else {
+            Slog.e(TAG, "Should not happen");
+        }
+        return null;
+    }
+
+    /**
+     * Search if any ip addresses are in watchlist.
+     *
+     * @param ipAddresses Ip address that you want to search in watchlist.
+     * @return Ip address that exists in watchlist, null if it does not match anything.
+     */
+    private String searchIpInWatchlist(String[] ipAddresses) {
+        for (String ipAddress : ipAddresses) {
+            if (isIpInWatchlist(ipAddress)) {
+                return ipAddress;
+            }
+        }
+        return null;
+    }
+
+    /** Search if the ip is in watchlist */
+    private boolean isIpInWatchlist(String ipAddr) {
+        if (ipAddr == null) {
+            return false;
+        }
+        return mSettings.containsIp(ipAddr);
+    }
+
+    /** Search if the host is in watchlist */
+    private boolean isHostInWatchlist(String host) {
+        if (host == null) {
+            return false;
+        }
+        return mSettings.containsDomain(host);
+    }
+
+    /**
+     * Search if any sub-domain in host is in watchlist.
+     *
+     * @param host Host that we want to search.
+     * @return Domain that exists in watchlist, null if it does not match anything.
+     */
+    private String searchAllSubDomainsInWatchlist(String host) {
+        if (host == null) {
+            return null;
+        }
+        final String[] subDomains = getAllSubDomains(host);
+        for (String subDomain : subDomains) {
+            if (isHostInWatchlist(subDomain)) {
+                return subDomain;
+            }
+        }
+        return null;
+    }
+
+    /** Get all sub-domains in a host */
+    @VisibleForTesting
+    static String[] getAllSubDomains(String host) {
+        if (host == null) {
+            return null;
+        }
+        final ArrayList<String> subDomainList = new ArrayList<>();
+        subDomainList.add(host);
+        int index = host.indexOf(".");
+        while (index != -1) {
+            host = host.substring(index + 1);
+            if (!TextUtils.isEmpty(host)) {
+                subDomainList.add(host);
+            }
+            index = host.indexOf(".");
+        }
+        return subDomainList.toArray(new String[0]);
+    }
+}
diff --git a/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/com/android/server/net/watchlist/WatchlistReportDbHelper.java
new file mode 100644
index 0000000..f48463f
--- /dev/null
+++ b/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Pair;
+
+import com.android.internal.util.HexDump;
+
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class to process watchlist read / save watchlist reports.
+ */
+class WatchlistReportDbHelper extends SQLiteOpenHelper {
+
+    private static final String TAG = "WatchlistReportDbHelper";
+
+    private static final String NAME = "watchlist_report.db";
+    private static final int VERSION = 2;
+
+    private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
+
+    private static class WhiteListReportContract {
+        private static final String TABLE = "records";
+        private static final String APP_DIGEST = "app_digest";
+        private static final String CNC_DOMAIN = "cnc_domain";
+        private static final String TIMESTAMP = "timestamp";
+    }
+
+    private static final String CREATE_TABLE_MODEL = "CREATE TABLE "
+            + WhiteListReportContract.TABLE + "("
+            + WhiteListReportContract.APP_DIGEST + " BLOB,"
+            + WhiteListReportContract.CNC_DOMAIN + " TEXT,"
+            + WhiteListReportContract.TIMESTAMP + " INTEGER DEFAULT 0" + " )";
+
+    private static final int INDEX_DIGEST = 0;
+    private static final int INDEX_CNC_DOMAIN = 1;
+    private static final int INDEX_TIMESTAMP = 2;
+
+    private static final String[] DIGEST_DOMAIN_PROJECTION =
+            new String[] {
+                    WhiteListReportContract.APP_DIGEST,
+                    WhiteListReportContract.CNC_DOMAIN
+            };
+
+    private static WatchlistReportDbHelper sInstance;
+
+    /**
+     * Aggregated watchlist records.
+     */
+    public static class AggregatedResult {
+        // A list of digests that visited c&c domain or ip before.
+        Set<String> appDigestList;
+
+        // The c&c domain or ip visited before.
+        String cncDomainVisited;
+
+        // A list of app digests and c&c domain visited.
+        HashMap<String, String> appDigestCNCList;
+    }
+
+    private WatchlistReportDbHelper(Context context) {
+        super(context, WatchlistSettings.getSystemWatchlistFile(NAME).getAbsolutePath(),
+                null, VERSION);
+        // Memory optimization - close idle connections after 30s of inactivity
+        setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+    }
+
+    public static synchronized WatchlistReportDbHelper getInstance(Context context) {
+        if (sInstance != null) {
+            return sInstance;
+        }
+        sInstance = new WatchlistReportDbHelper(context);
+        return sInstance;
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(CREATE_TABLE_MODEL);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // TODO: For now, drop older tables and recreate new ones.
+        db.execSQL("DROP TABLE IF EXISTS " + WhiteListReportContract.TABLE);
+        onCreate(db);
+    }
+
+    /**
+     * Insert new watchlist record.
+     *
+     * @param appDigest The digest of an app.
+     * @param cncDomain C&C domain that app visited.
+     * @return True if success.
+     */
+    public boolean insertNewRecord(byte[] appDigest, String cncDomain,
+            long timestamp) {
+        final SQLiteDatabase db = getWritableDatabase();
+        final ContentValues values = new ContentValues();
+        values.put(WhiteListReportContract.APP_DIGEST, appDigest);
+        values.put(WhiteListReportContract.CNC_DOMAIN, cncDomain);
+        values.put(WhiteListReportContract.TIMESTAMP, timestamp);
+        return db.insert(WhiteListReportContract.TABLE, null, values) != -1;
+    }
+
+    /**
+     * Aggregate the records in database, and return a rappor encoded result.
+     */
+    public AggregatedResult getAggregatedRecords() {
+        final long twoDaysBefore = getTwoDaysBeforeTimestamp();
+        final long yesterday = getYesterdayTimestamp();
+        final String selectStatement = WhiteListReportContract.TIMESTAMP + " >= ? AND " +
+                WhiteListReportContract.TIMESTAMP + " <= ?";
+
+        final SQLiteDatabase db = getReadableDatabase();
+        Cursor c = null;
+        try {
+            c = db.query(true /* distinct */,
+                    WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
+                    new String[]{"" + twoDaysBefore, "" + yesterday}, null, null,
+                    null, null);
+            if (c == null || c.getCount() == 0) {
+                return null;
+            }
+            final AggregatedResult result = new AggregatedResult();
+            result.cncDomainVisited = null;
+            // After aggregation, each digest maximum will have only 1 record.
+            result.appDigestList = new HashSet<>();
+            result.appDigestCNCList = new HashMap<>();
+            while (c.moveToNext()) {
+                // We use hex string here as byte[] cannot be a key in HashMap.
+                String digestHexStr = HexDump.toHexString(c.getBlob(INDEX_DIGEST));
+                String cncDomain = c.getString(INDEX_CNC_DOMAIN);
+
+                result.appDigestList.add(digestHexStr);
+                if (result.cncDomainVisited != null) {
+                    result.cncDomainVisited = cncDomain;
+                }
+                result.appDigestCNCList.put(digestHexStr, cncDomain);
+            }
+            return result;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    /**
+     * Remove all the records before yesterday.
+     *
+     * @return True if success.
+     */
+    public boolean cleanup() {
+        final SQLiteDatabase db = getWritableDatabase();
+        final long twoDaysBefore = getTwoDaysBeforeTimestamp();
+        final String clause = WhiteListReportContract.TIMESTAMP + "< " + twoDaysBefore;
+        return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
+    }
+
+    static long getTwoDaysBeforeTimestamp() {
+        return getMidnightTimestamp(2);
+    }
+
+    static long getYesterdayTimestamp() {
+        return getMidnightTimestamp(1);
+    }
+
+    static long getMidnightTimestamp(int daysBefore) {
+        java.util.Calendar date = new GregorianCalendar();
+        // reset hour, minutes, seconds and millis
+        date.set(java.util.Calendar.HOUR_OF_DAY, 0);
+        date.set(java.util.Calendar.MINUTE, 0);
+        date.set(java.util.Calendar.SECOND, 0);
+        date.set(java.util.Calendar.MILLISECOND, 0);
+        date.add(java.util.Calendar.DAY_OF_MONTH, -daysBefore);
+        return date.getTimeInMillis();
+    }
+}
\ No newline at end of file
diff --git a/com/android/server/net/watchlist/WatchlistSettings.java b/com/android/server/net/watchlist/WatchlistSettings.java
new file mode 100644
index 0000000..c50f0d5
--- /dev/null
+++ b/com/android/server/net/watchlist/WatchlistSettings.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.CRC32;
+
+/**
+ * A util class to do watchlist settings operations, like setting watchlist, query if a domain
+ * exists in watchlist.
+ */
+class WatchlistSettings {
+    private static final String TAG = "WatchlistSettings";
+
+    // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml
+    static final String SYSTEM_WATCHLIST_DIR = "network_watchlist";
+
+    private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml";
+
+    private static class XmlTags {
+        private static final String WATCHLIST_SETTINGS = "watchlist-settings";
+        private static final String SHA256_DOMAIN = "sha256-domain";
+        private static final String CRC32_DOMAIN = "crc32-domain";
+        private static final String SHA256_IP = "sha256-ip";
+        private static final String CRC32_IP = "crc32-ip";
+        private static final String HASH = "hash";
+    }
+
+    private static WatchlistSettings sInstance = new WatchlistSettings();
+    private final AtomicFile mXmlFile;
+    private final Object mLock = new Object();
+    private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>());
+    private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>());
+    private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>());
+    private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>());
+
+    public static synchronized WatchlistSettings getInstance() {
+        return sInstance;
+    }
+
+    private WatchlistSettings() {
+        this(getSystemWatchlistFile(WATCHLIST_XML_FILE));
+    }
+
+    @VisibleForTesting
+    protected WatchlistSettings(File xmlFile) {
+        mXmlFile = new AtomicFile(xmlFile);
+        readSettingsLocked();
+    }
+
+    static File getSystemWatchlistFile(String filename) {
+        final File dataSystemDir = Environment.getDataSystemDirectory();
+        final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR);
+        systemWatchlistDir.mkdirs();
+        return new File(systemWatchlistDir, filename);
+    }
+
+    private void readSettingsLocked() {
+        synchronized (mLock) {
+            FileInputStream stream;
+            try {
+                stream = mXmlFile.openRead();
+            } catch (FileNotFoundException e) {
+                Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath());
+                return;
+            }
+
+            final List<byte[]> crc32DomainList = new ArrayList<>();
+            final List<byte[]> sha256DomainList = new ArrayList<>();
+            final List<byte[]> crc32IpList = new ArrayList<>();
+            final List<byte[]> sha256IpList = new ArrayList<>();
+
+            try {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(stream, StandardCharsets.UTF_8.name());
+                parser.nextTag();
+                parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+                while (parser.nextTag() == XmlPullParser.START_TAG) {
+                    String tagName = parser.getName();
+                    switch (tagName) {
+                        case XmlTags.CRC32_DOMAIN:
+                            parseHash(parser, tagName, crc32DomainList);
+                            break;
+                        case XmlTags.CRC32_IP:
+                            parseHash(parser, tagName, crc32IpList);
+                            break;
+                        case XmlTags.SHA256_DOMAIN:
+                            parseHash(parser, tagName, sha256DomainList);
+                            break;
+                        case XmlTags.SHA256_IP:
+                            parseHash(parser, tagName, sha256IpList);
+                            break;
+                        default:
+                            Log.w(TAG, "Unknown element: " + parser.getName());
+                            XmlUtils.skipCurrentTag(parser);
+                    }
+                }
+                parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+                writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
+            } catch (IllegalStateException | NullPointerException | NumberFormatException |
+                    XmlPullParserException | IOException | IndexOutOfBoundsException e) {
+                Log.w(TAG, "Failed parsing " + e);
+            } finally {
+                try {
+                    stream.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    private void parseHash(XmlPullParser parser, String tagName, List<byte[]> hashSet)
+            throws IOException, XmlPullParserException {
+        parser.require(XmlPullParser.START_TAG, null, tagName);
+        while (parser.nextTag() == XmlPullParser.START_TAG) {
+            parser.require(XmlPullParser.START_TAG, null, XmlTags.HASH);
+            byte[] hash = HexDump.hexStringToByteArray(parser.nextText());
+            parser.require(XmlPullParser.END_TAG, null, XmlTags.HASH);
+            hashSet.add(hash);
+        }
+        parser.require(XmlPullParser.END_TAG, null, tagName);
+    }
+
+    /**
+     * Write network watchlist settings to disk.
+     * Adb should not use it, should use writeSettingsToMemory directly instead.
+     */
+    public void writeSettingsToDisk(List<byte[]> newCrc32DomainList,
+            List<byte[]> newSha256DomainList,
+            List<byte[]> newCrc32IpList,
+            List<byte[]> newSha256IpList) {
+        synchronized (mLock) {
+            FileOutputStream stream;
+            try {
+                stream = mXmlFile.startWrite();
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to write display settings: " + e);
+                return;
+            }
+
+            try {
+                XmlSerializer out = new FastXmlSerializer();
+                out.setOutput(stream, StandardCharsets.UTF_8.name());
+                out.startDocument(null, true);
+                out.startTag(null, XmlTags.WATCHLIST_SETTINGS);
+
+                writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList);
+                writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList);
+                writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList);
+                writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList);
+
+                out.endTag(null, XmlTags.WATCHLIST_SETTINGS);
+                out.endDocument();
+                mXmlFile.finishWrite(stream);
+                writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList,
+                        newSha256IpList);
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to write display settings, restoring backup.", e);
+                mXmlFile.failWrite(stream);
+            }
+        }
+    }
+
+    /**
+     * Write network watchlist settings to memory.
+     */
+    public void writeSettingsToMemory(List<byte[]> newCrc32DomainList,
+            List<byte[]> newSha256DomainList,
+            List<byte[]> newCrc32IpList,
+            List<byte[]> newSha256IpList) {
+        synchronized (mLock) {
+            mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList);
+            mCrc32IpDigests = new HarmfulDigests(newCrc32IpList);
+            mSha256DomainDigests = new HarmfulDigests(newSha256DomainList);
+            mSha256IpDigests = new HarmfulDigests(newSha256IpList);
+        }
+    }
+
+    private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet)
+            throws IOException {
+        out.startTag(null, tagName);
+        for (byte[] hash : hashSet) {
+            out.startTag(null, XmlTags.HASH);
+            out.text(HexDump.toHexString(hash));
+            out.endTag(null, XmlTags.HASH);
+        }
+        out.endTag(null, tagName);
+    }
+
+    public boolean containsDomain(String domain) {
+        // First it does a quick CRC32 check.
+        final byte[] crc32 = getCrc32(domain);
+        if (!mCrc32DomainDigests.contains(crc32)) {
+            return false;
+        }
+        // Now we do a slow SHA256 check.
+        final byte[] sha256 = getSha256(domain);
+        return mSha256DomainDigests.contains(sha256);
+    }
+
+    public boolean containsIp(String ip) {
+        // First it does a quick CRC32 check.
+        final byte[] crc32 = getCrc32(ip);
+        if (!mCrc32IpDigests.contains(crc32)) {
+            return false;
+        }
+        // Now we do a slow SHA256 check.
+        final byte[] sha256 = getSha256(ip);
+        return mSha256IpDigests.contains(sha256);
+    }
+
+
+    /** Get CRC32 of a string */
+    private byte[] getCrc32(String str) {
+        final CRC32 crc = new CRC32();
+        crc.update(str.getBytes());
+        final long tmp = crc.getValue();
+        return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255),
+                (byte)(tmp >> 8 & 255), (byte)(tmp & 255)};
+    }
+
+    /** Get SHA256 of a string */
+    private byte[] getSha256(String str) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA256");
+        } catch (NoSuchAlgorithmException e) {
+            /* can't happen */
+            return null;
+        }
+        messageDigest.update(str.getBytes());
+        return messageDigest.digest();
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Domain CRC32 digest list:");
+        mCrc32DomainDigests.dump(fd, pw, args);
+        pw.println("Domain SHA256 digest list:");
+        mSha256DomainDigests.dump(fd, pw, args);
+        pw.println("Ip CRC32 digest list:");
+        mCrc32IpDigests.dump(fd, pw, args);
+        pw.println("Ip SHA256 digest list:");
+        mSha256IpDigests.dump(fd, pw, args);
+    }
+}
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index 238d87b..557ba42 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -460,7 +460,7 @@
                 mRankingHelper.readXml(parser, forRestore);
             }
             // No non-system managed services are allowed on low ram devices
-            if (!ActivityManager.isLowRamDeviceStatic()) {
+            if (canUseManagedServices()) {
                 if (mListeners.getConfig().xmlTag.equals(parser.getName())) {
                     mListeners.readXml(parser);
                     migratedManagedServices = true;
@@ -548,12 +548,6 @@
         out.endDocument();
     }
 
-    /** Use this to check if a package can post a notification or toast. */
-    private boolean checkNotificationOp(String pkg, int uid) {
-        return mAppOps.checkOp(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
-                == AppOpsManager.MODE_ALLOWED && !isPackageSuspendedForUser(pkg, uid);
-    }
-
     private static final class ToastRecord
     {
         final int pid;
@@ -1226,7 +1220,6 @@
         mAccessibilityManager = am;
     }
 
-
     // TODO: All tests should use this init instead of the one-off setters above.
     @VisibleForTesting
     void init(Looper looper, IPackageManager packageManager,
@@ -2727,9 +2720,9 @@
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
             final DumpFilter filter = DumpFilter.parseFromArguments(args);
-            if (filter != null && filter.stats) {
+            if (filter.stats) {
                 dumpJson(pw, filter);
-            } else if (filter != null && filter.proto) {
+            } else if (filter.proto) {
                 dumpProto(fd, filter);
             } else {
                 dumpImpl(pw, filter);
@@ -2818,19 +2811,25 @@
         @Override
         public void setNotificationPolicyAccessGranted(String pkg, boolean granted)
                 throws RemoteException {
+            setNotificationPolicyAccessGrantedForUser(
+                    pkg, getCallingUserHandle().getIdentifier(), granted);
+        }
+
+        @Override
+        public void setNotificationPolicyAccessGrantedForUser(
+                String pkg, int userId, boolean granted) {
             checkCallerIsSystemOrShell();
             final long identity = Binder.clearCallingIdentity();
             try {
-                if (!mActivityManager.isLowRamDevice()) {
+                if (canUseManagedServices()) {
                     mConditionProviders.setPackageOrComponentEnabled(
-                            pkg, getCallingUserHandle().getIdentifier(), true, granted);
+                            pkg, userId, true, granted);
 
                     getContext().sendBroadcastAsUser(new Intent(
                             NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
                                     .setPackage(pkg)
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
-                            getCallingUserHandle(), null);
-
+                            UserHandle.of(userId), null);
                     savePolicyFile();
                 }
             } finally {
@@ -2840,7 +2839,6 @@
 
         @Override
         public Policy getNotificationPolicy(String pkg) {
-            enforcePolicyAccess(pkg, "getNotificationPolicy");
             final long identity = Binder.clearCallingIdentity();
             try {
                 return mZenModeHelper.getNotificationPolicy();
@@ -2918,18 +2916,17 @@
             checkCallerIsSystemOrShell();
             final long identity = Binder.clearCallingIdentity();
             try {
-                if (!mActivityManager.isLowRamDevice()) {
+                if (canUseManagedServices()) {
                     mConditionProviders.setPackageOrComponentEnabled(listener.flattenToString(),
                             userId, false, granted);
                     mListeners.setPackageOrComponentEnabled(listener.flattenToString(),
                             userId, true, granted);
 
                     getContext().sendBroadcastAsUser(new Intent(
-                                    NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
-
+                            NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
                                     .setPackage(listener.getPackageName())
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
-                            getCallingUserHandle(), null);
+                            UserHandle.of(userId), null);
 
                     savePolicyFile();
                 }
@@ -2945,7 +2942,7 @@
             checkCallerIsSystemOrShell();
             final long identity = Binder.clearCallingIdentity();
             try {
-                if (!mActivityManager.isLowRamDevice()) {
+                if (canUseManagedServices()) {
                     mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(),
                             userId, false, granted);
                     mAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
@@ -2955,7 +2952,7 @@
                             NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
                                     .setPackage(assistant.getPackageName())
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
-                            getCallingUserHandle(), null);
+                            UserHandle.of(userId), null);
 
                     savePolicyFile();
                 }
@@ -3238,7 +3235,7 @@
         return null;
     };
 
-    private void dumpJson(PrintWriter pw, DumpFilter filter) {
+    private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter) {
         JSONObject dump = new JSONObject();
         try {
             dump.put("service", "Notification Manager");
@@ -3252,7 +3249,7 @@
         pw.println(dump);
     }
 
-    private void dumpProto(FileDescriptor fd, DumpFilter filter) {
+    private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter) {
         final ProtoOutputStream proto = new ProtoOutputStream(fd);
         synchronized (mNotificationLock) {
             long records = proto.start(NotificationServiceDumpProto.RECORDS);
@@ -3334,7 +3331,7 @@
         proto.flush();
     }
 
-    void dumpImpl(PrintWriter pw, DumpFilter filter) {
+    void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter) {
         pw.print("Current Notification Manager state");
         if (filter.filtered) {
             pw.print(" (filtered to "); pw.print(filter); pw.print(")");
@@ -5434,6 +5431,11 @@
         }
     }
 
+    private boolean canUseManagedServices() {
+        return !mActivityManager.isLowRamDevice()
+                || mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
     private class TrimCache {
         StatusBarNotification heavy;
         StatusBarNotification sbnClone;
@@ -5898,6 +5900,7 @@
         public boolean redact = true;
         public boolean proto = false;
 
+        @NonNull
         public static DumpFilter parseFromArguments(String[] args) {
             final DumpFilter filter = new DumpFilter();
             for (int ai = 0; ai < args.length; ai++) {
diff --git a/com/android/server/notification/RankingHelper.java b/com/android/server/notification/RankingHelper.java
index d7e9cf3..d566a45 100644
--- a/com/android/server/notification/RankingHelper.java
+++ b/com/android/server/notification/RankingHelper.java
@@ -22,6 +22,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
@@ -915,21 +916,21 @@
         }
     }
 
-    public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
-        if (filter == null) {
-            final int N = mSignalExtractors.length;
+    public void dump(PrintWriter pw, String prefix,
+            @NonNull NotificationManagerService.DumpFilter filter) {
+        final int N = mSignalExtractors.length;
+        pw.print(prefix);
+        pw.print("mSignalExtractors.length = ");
+        pw.println(N);
+        for (int i = 0; i < N; i++) {
             pw.print(prefix);
-            pw.print("mSignalExtractors.length = ");
-            pw.println(N);
-            for (int i = 0; i < N; i++) {
-                pw.print(prefix);
-                pw.print("  ");
-                pw.println(mSignalExtractors[i]);
-            }
-
-            pw.print(prefix);
-            pw.println("per-package config:");
+            pw.print("  ");
+            pw.println(mSignalExtractors[i].getClass().getSimpleName());
         }
+
+        pw.print(prefix);
+        pw.println("per-package config:");
+
         pw.println("Records:");
         synchronized (mRecords) {
             dumpRecords(pw, prefix, filter, mRecords);
@@ -938,7 +939,8 @@
         dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
     }
 
-    public void dump(ProtoOutputStream proto, NotificationManagerService.DumpFilter filter) {
+    public void dump(ProtoOutputStream proto,
+            @NonNull NotificationManagerService.DumpFilter filter) {
         final int N = mSignalExtractors.length;
         for (int i = 0; i < N; i++) {
             proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
@@ -952,12 +954,13 @@
     }
 
     private static void dumpRecords(ProtoOutputStream proto, long fieldId,
-            NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
+            @NonNull NotificationManagerService.DumpFilter filter,
+            ArrayMap<String, Record> records) {
         final int N = records.size();
         long fToken;
         for (int i = 0; i < N; i++) {
             final Record r = records.valueAt(i);
-            if (filter == null || filter.matches(r.pkg)) {
+            if (filter.matches(r.pkg)) {
                 fToken = proto.start(fieldId);
 
                 proto.write(RecordProto.PACKAGE, r.pkg);
@@ -985,11 +988,12 @@
     }
 
     private static void dumpRecords(PrintWriter pw, String prefix,
-            NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
+            @NonNull NotificationManagerService.DumpFilter filter,
+            ArrayMap<String, Record> records) {
         final int N = records.size();
         for (int i = 0; i < N; i++) {
             final Record r = records.valueAt(i);
-            if (filter == null || filter.matches(r.pkg)) {
+            if (filter.matches(r.pkg)) {
                 pw.print(prefix);
                 pw.print("  AppSettings: ");
                 pw.print(r.pkg);
diff --git a/com/android/server/notification/ZenModeHelper.java b/com/android/server/notification/ZenModeHelper.java
index f61cec9..1e9fab5 100644
--- a/com/android/server/notification/ZenModeHelper.java
+++ b/com/android/server/notification/ZenModeHelper.java
@@ -835,7 +835,8 @@
         // alarm restrictions
         final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
         // total silence restrictions
-        final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+        final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+                || areAllBehaviorSoundsMuted();
 
         for (int usage : AudioAttributes.SDK_USAGES) {
             final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
@@ -855,9 +856,11 @@
         }
     }
 
+
     @VisibleForTesting
     protected void applyRestrictions(boolean mute, int usage) {
         final String[] exceptionPackages = null; // none (for now)
+
         mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
                 mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
                 exceptionPackages);
@@ -866,6 +869,12 @@
                 exceptionPackages);
     }
 
+    private boolean areAllBehaviorSoundsMuted() {
+        return !mConfig.allowAlarms  && !mConfig.allowMediaSystemOther && !mConfig.allowReminders
+                && !mConfig.allowCalls && !mConfig.allowMessages && !mConfig.allowEvents
+                && !mConfig.allowRepeatCallers;
+    }
+
     private void applyZenToRingerMode() {
         if (mAudioManager == null) return;
         // force the ringer mode into compliance
diff --git a/com/android/server/pm/KeySetManagerService.java b/com/android/server/pm/KeySetManagerService.java
index 3574466..fca9585 100644
--- a/com/android/server/pm/KeySetManagerService.java
+++ b/com/android/server/pm/KeySetManagerService.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 
+import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
+
 import com.android.internal.util.Preconditions;
 import android.content.pm.PackageParser;
 import android.util.ArrayMap;
@@ -341,6 +343,41 @@
         return mKeySets.get(id) != null;
     }
 
+    public boolean shouldCheckUpgradeKeySetLocked(PackageSettingBase oldPs, int scanFlags) {
+        // Can't rotate keys during boot or if sharedUser.
+        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
+                || !oldPs.keySetData.isUsingUpgradeKeySets()) {
+            return false;
+        }
+        // app is using upgradeKeySets; make sure all are valid
+        long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
+        for (int i = 0; i < upgradeKeySets.length; i++) {
+            if (!isIdValidKeySetId(upgradeKeySets[i])) {
+                Slog.wtf(TAG, "Package "
+                         + (oldPs.name != null ? oldPs.name : "<null>")
+                         + " contains upgrade-key-set reference to unknown key-set: "
+                         + upgradeKeySets[i]
+                         + " reverting to signatures check.");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean checkUpgradeKeySetLocked(PackageSettingBase oldPS,
+            PackageParser.Package newPkg) {
+        // Upgrade keysets are being used.  Determine if new package has a superset of the
+        // required keys.
+        long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
+        for (int i = 0; i < upgradeKeySets.length; i++) {
+            Set<PublicKey> upgradeSet = getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
+            if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Fetches the {@link PublicKey public keys} which belong to the specified
      * KeySet id.
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 86a1c03..29f48ee 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -110,9 +110,9 @@
             return false;
         }
 
-        // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
+        // We do not dexopt a priv-app package when pm.dexopt.priv-apps-oob is true.
         if (pkg.isPrivileged()) {
-            return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
+            return !SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false);
         }
 
         return true;
@@ -209,7 +209,7 @@
 
             // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct
             // flags.
-            final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete());
+            final int dexoptFlags = getDexFlags(pkg, compilerFilter, options);
 
             for (String dexCodeIsa : dexCodeInstructionSets) {
                 int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
@@ -349,8 +349,7 @@
                 dexUseInfo.isUsedByOtherApps());
         // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
         // Secondary dex files are currently not compiled at boot.
-        int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true)
-                | DEXOPT_SECONDARY_DEX;
+        int dexoptFlags = getDexFlags(info, compilerFilter, options) | DEXOPT_SECONDARY_DEX;
         // Check the app storage and add the appropriate flags.
         if (info.deviceProtectedDataDir != null &&
                 FileUtils.contains(info.deviceProtectedDataDir, path)) {
@@ -486,11 +485,11 @@
      * filter.
      */
     private int getDexFlags(PackageParser.Package pkg, String compilerFilter,
-            boolean bootComplete) {
-        return getDexFlags(pkg.applicationInfo, compilerFilter, bootComplete);
+            DexoptOptions options) {
+        return getDexFlags(pkg.applicationInfo, compilerFilter, options);
     }
 
-    private int getDexFlags(ApplicationInfo info, String compilerFilter, boolean bootComplete) {
+    private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) {
         int flags = info.flags;
         boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
         // Profile guide compiled oat files should not be public.
@@ -501,7 +500,8 @@
                 (isPublic ? DEXOPT_PUBLIC : 0)
                 | (debuggable ? DEXOPT_DEBUGGABLE : 0)
                 | profileFlag
-                | (bootComplete ? DEXOPT_BOOTCOMPLETE : 0);
+                | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
+                | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0);
         return adjustDexoptFlags(dexFlags);
     }
 
diff --git a/com/android/server/pm/PackageInstallerService.java b/com/android/server/pm/PackageInstallerService.java
index 09f9cb8..be9b2f3 100644
--- a/com/android/server/pm/PackageInstallerService.java
+++ b/com/android/server/pm/PackageInstallerService.java
@@ -32,6 +32,7 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageInstallerSession;
@@ -45,6 +46,7 @@
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
@@ -724,6 +726,12 @@
                 Binder.restoreCallingIdentity(ident);
             }
         } else {
+            ApplicationInfo appInfo = mPm.getApplicationInfo(callerPackageName, 0, userId);
+            if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
+                mContext.enforceCallingOrSelfPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
+                        null);
+            }
+
             // Take a short detour to confirm with user
             final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
             intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index 7be0cde..83cffe5 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -102,6 +102,15 @@
 import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
+import static com.android.server.pm.PackageManagerServiceUtils.decompressFile;
+import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
+import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles;
+import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
 import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
@@ -408,7 +417,7 @@
     private static final boolean DEBUG_FILTERS = false;
     public static final boolean DEBUG_PERMISSIONS = false;
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
-    private static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
+    public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
 
     // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
     // and PackageDexOptimizer. All these classes have their own flag to allow switching a single
@@ -462,9 +471,9 @@
 
     private static final String STATIC_SHARED_LIB_DELIMITER = "_";
     /** Extension of the compressed packages */
-    private final static String COMPRESSED_EXTENSION = ".gz";
+    public final static String COMPRESSED_EXTENSION = ".gz";
     /** Suffix of stub packages on the system partition */
-    private final static String STUB_SUFFIX = "-Stub";
+    public final static String STUB_SUFFIX = "-Stub";
 
     private static final int[] EMPTY_INT_ARRAY = new int[0];
 
@@ -542,6 +551,8 @@
 
     private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
 
+    private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
+
     /** Canonical intent used to identify what counts as a "web browser" app */
     private static final Intent sBrowserIntent;
     static {
@@ -574,37 +585,6 @@
 
     public static final int REASON_LAST = REASON_SHARED;
 
-    /** All dangerous permission names in the same order as the events in MetricsEvent */
-    private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
-            Manifest.permission.READ_CALENDAR,
-            Manifest.permission.WRITE_CALENDAR,
-            Manifest.permission.CAMERA,
-            Manifest.permission.READ_CONTACTS,
-            Manifest.permission.WRITE_CONTACTS,
-            Manifest.permission.GET_ACCOUNTS,
-            Manifest.permission.ACCESS_FINE_LOCATION,
-            Manifest.permission.ACCESS_COARSE_LOCATION,
-            Manifest.permission.RECORD_AUDIO,
-            Manifest.permission.READ_PHONE_STATE,
-            Manifest.permission.CALL_PHONE,
-            Manifest.permission.READ_CALL_LOG,
-            Manifest.permission.WRITE_CALL_LOG,
-            Manifest.permission.ADD_VOICEMAIL,
-            Manifest.permission.USE_SIP,
-            Manifest.permission.PROCESS_OUTGOING_CALLS,
-            Manifest.permission.READ_CELL_BROADCASTS,
-            Manifest.permission.BODY_SENSORS,
-            Manifest.permission.SEND_SMS,
-            Manifest.permission.RECEIVE_SMS,
-            Manifest.permission.READ_SMS,
-            Manifest.permission.RECEIVE_WAP_PUSH,
-            Manifest.permission.RECEIVE_MMS,
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            Manifest.permission.READ_PHONE_NUMBERS,
-            Manifest.permission.ANSWER_PHONE_CALLS);
-
-
     /**
      * Version number for the package parser cache. Increment this whenever the format or
      * extent of cached data changes. See {@code PackageParser#setCacheDir}.
@@ -3107,75 +3087,6 @@
         }
     }
 
-    private int decompressFile(File srcFile, File dstFile) throws ErrnoException {
-        if (DEBUG_COMPRESSION) {
-            Slog.i(TAG, "Decompress file"
-                    + "; src: " + srcFile.getAbsolutePath()
-                    + ", dst: " + dstFile.getAbsolutePath());
-        }
-        try (
-                InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
-                OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
-        ) {
-            Streams.copy(fileIn, fileOut);
-            Os.chmod(dstFile.getAbsolutePath(), 0644);
-            return PackageManager.INSTALL_SUCCEEDED;
-        } catch (IOException e) {
-            logCriticalInfo(Log.ERROR, "Failed to decompress file"
-                    + "; src: " + srcFile.getAbsolutePath()
-                    + ", dst: " + dstFile.getAbsolutePath());
-        }
-        return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-    }
-
-    private File[] getCompressedFiles(String codePath) {
-        final File stubCodePath = new File(codePath);
-        final String stubName = stubCodePath.getName();
-
-        // The layout of a compressed package on a given partition is as follows :
-        //
-        // Compressed artifacts:
-        //
-        // /partition/ModuleName/foo.gz
-        // /partation/ModuleName/bar.gz
-        //
-        // Stub artifact:
-        //
-        // /partition/ModuleName-Stub/ModuleName-Stub.apk
-        //
-        // In other words, stub is on the same partition as the compressed artifacts
-        // and in a directory that's suffixed with "-Stub".
-        int idx = stubName.lastIndexOf(STUB_SUFFIX);
-        if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
-            return null;
-        }
-
-        final File stubParentDir = stubCodePath.getParentFile();
-        if (stubParentDir == null) {
-            Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
-            return null;
-        }
-
-        final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
-        final File[] files = compressedPath.listFiles(new FilenameFilter() {
-            @Override
-            public boolean accept(File dir, String name) {
-                return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
-            }
-        });
-
-        if (DEBUG_COMPRESSION && files != null && files.length > 0) {
-            Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
-        }
-
-        return files;
-    }
-
-    private boolean compressedFileExists(String codePath) {
-        final File[] compressedFiles = getCompressedFiles(codePath);
-        return compressedFiles != null && compressedFiles.length > 0;
-    }
-
     /**
      * Decompresses the given package on the system image onto
      * the /data partition.
@@ -5176,66 +5087,6 @@
                 getCallingUid(), userId, mPermissionCallback);
     }
 
-    /**
-     * Get the first event id for the permission.
-     *
-     * <p>There are four events for each permission: <ul>
-     *     <li>Request permission: first id + 0</li>
-     *     <li>Grant permission: first id + 1</li>
-     *     <li>Request for permission denied: first id + 2</li>
-     *     <li>Revoke permission: first id + 3</li>
-     * </ul></p>
-     *
-     * @param name name of the permission
-     *
-     * @return The first event id for the permission
-     */
-    private static int getBaseEventId(@NonNull String name) {
-        int eventIdIndex = ALL_DANGEROUS_PERMISSIONS.indexOf(name);
-
-        if (eventIdIndex == -1) {
-            if (AppOpsManager.permissionToOpCode(name) == AppOpsManager.OP_NONE
-                    || Build.IS_USER) {
-                Log.i(TAG, "Unknown permission " + name);
-
-                return MetricsEvent.ACTION_PERMISSION_REQUEST_UNKNOWN;
-            } else {
-                // Most likely #ALL_DANGEROUS_PERMISSIONS needs to be updated.
-                //
-                // Also update
-                // - EventLogger#ALL_DANGEROUS_PERMISSIONS
-                // - metrics_constants.proto
-                throw new IllegalStateException("Unknown permission " + name);
-            }
-        }
-
-        return MetricsEvent.ACTION_PERMISSION_REQUEST_READ_CALENDAR + eventIdIndex * 4;
-    }
-
-    /**
-     * Log that a permission was revoked.
-     *
-     * @param context Context of the caller
-     * @param name name of the permission
-     * @param packageName package permission if for
-     */
-    private static void logPermissionRevoked(@NonNull Context context, @NonNull String name,
-            @NonNull String packageName) {
-        MetricsLogger.action(context, getBaseEventId(name) + 3, packageName);
-    }
-
-    /**
-     * Log that a permission request was granted.
-     *
-     * @param context Context of the caller
-     * @param name name of the permission
-     * @param packageName package permission if for
-     */
-    private static void logPermissionGranted(@NonNull Context context, @NonNull String name,
-            @NonNull String packageName) {
-        MetricsLogger.action(context, getBaseEventId(name) + 1, packageName);
-    }
-
     @Override
     public void resetRuntimePermissions() {
         mContext.enforceCallingOrSelfPermission(
@@ -5476,56 +5327,6 @@
     }
 
     /**
-     * Compares two sets of signatures. Returns:
-     * <br />
-     * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
-     * <br />
-     * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
-     * <br />
-     * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
-     * <br />
-     * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
-     * <br />
-     * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
-     */
-    public static int compareSignatures(Signature[] s1, Signature[] s2) {
-        if (s1 == null) {
-            return s2 == null
-                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
-                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
-        }
-
-        if (s2 == null) {
-            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
-        }
-
-        if (s1.length != s2.length) {
-            return PackageManager.SIGNATURE_NO_MATCH;
-        }
-
-        // Since both signature sets are of size 1, we can compare without HashSets.
-        if (s1.length == 1) {
-            return s1[0].equals(s2[0]) ?
-                    PackageManager.SIGNATURE_MATCH :
-                    PackageManager.SIGNATURE_NO_MATCH;
-        }
-
-        ArraySet<Signature> set1 = new ArraySet<Signature>();
-        for (Signature sig : s1) {
-            set1.add(sig);
-        }
-        ArraySet<Signature> set2 = new ArraySet<Signature>();
-        for (Signature sig : s2) {
-            set2.add(sig);
-        }
-        // Make sure s2 contains all signatures in s1.
-        if (set1.equals(set2)) {
-            return PackageManager.SIGNATURE_MATCH;
-        }
-        return PackageManager.SIGNATURE_NO_MATCH;
-    }
-
-    /**
      * If the database version for this type of package (internal storage or
      * external storage) is less than the version where package signatures
      * were updated, return true.
@@ -5535,76 +5336,11 @@
         return ver.databaseVersion < DatabaseVersion.SIGNATURE_END_ENTITY;
     }
 
-    /**
-     * Used for backward compatibility to make sure any packages with
-     * certificate chains get upgraded to the new style. {@code existingSigs}
-     * will be in the old format (since they were stored on disk from before the
-     * system upgrade) and {@code scannedSigs} will be in the newer format.
-     */
-    private int compareSignaturesCompat(PackageSignatures existingSigs,
-            PackageParser.Package scannedPkg) {
-        if (!isCompatSignatureUpdateNeeded(scannedPkg)) {
-            return PackageManager.SIGNATURE_NO_MATCH;
-        }
-
-        ArraySet<Signature> existingSet = new ArraySet<Signature>();
-        for (Signature sig : existingSigs.mSignatures) {
-            existingSet.add(sig);
-        }
-        ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
-        for (Signature sig : scannedPkg.mSignatures) {
-            try {
-                Signature[] chainSignatures = sig.getChainSignatures();
-                for (Signature chainSig : chainSignatures) {
-                    scannedCompatSet.add(chainSig);
-                }
-            } catch (CertificateEncodingException e) {
-                scannedCompatSet.add(sig);
-            }
-        }
-        /*
-         * Make sure the expanded scanned set contains all signatures in the
-         * existing one.
-         */
-        if (scannedCompatSet.equals(existingSet)) {
-            // Migrate the old signatures to the new scheme.
-            existingSigs.assignSignatures(scannedPkg.mSignatures);
-            // The new KeySets will be re-added later in the scanning process.
-            synchronized (mPackages) {
-                mSettings.mKeySetManagerService.removeAppKeySetDataLPw(scannedPkg.packageName);
-            }
-            return PackageManager.SIGNATURE_MATCH;
-        }
-        return PackageManager.SIGNATURE_NO_MATCH;
-    }
-
     private boolean isRecoverSignatureUpdateNeeded(PackageParser.Package scannedPkg) {
         final VersionInfo ver = getSettingsVersionForPackage(scannedPkg);
         return ver.databaseVersion < DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
     }
 
-    private int compareSignaturesRecover(PackageSignatures existingSigs,
-            PackageParser.Package scannedPkg) {
-        if (!isRecoverSignatureUpdateNeeded(scannedPkg)) {
-            return PackageManager.SIGNATURE_NO_MATCH;
-        }
-
-        String msg = null;
-        try {
-            if (Signature.areEffectiveMatch(existingSigs.mSignatures, scannedPkg.mSignatures)) {
-                logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for "
-                        + scannedPkg.packageName);
-                return PackageManager.SIGNATURE_MATCH;
-            }
-        } catch (CertificateException e) {
-            msg = e.getMessage();
-        }
-
-        logCriticalInfo(Log.INFO,
-                "Failed to recover certificates for " + scannedPkg.packageName + ": " + msg);
-        return PackageManager.SIGNATURE_NO_MATCH;
-    }
-
     @Override
     public List<String> getAllPackages() {
         final int callingUid = Binder.getCallingUid();
@@ -8328,51 +8064,10 @@
         parallelPackageParser.close();
     }
 
-    private static File getSettingsProblemFile() {
-        File dataDir = Environment.getDataDirectory();
-        File systemDir = new File(dataDir, "system");
-        File fname = new File(systemDir, "uiderrors.txt");
-        return fname;
-    }
-
     public static void reportSettingsProblem(int priority, String msg) {
         logCriticalInfo(priority, msg);
     }
 
-    public static void logCriticalInfo(int priority, String msg) {
-        Slog.println(priority, TAG, msg);
-        EventLogTags.writePmCriticalInfo(msg);
-        try {
-            File fname = getSettingsProblemFile();
-            FileOutputStream out = new FileOutputStream(fname, true);
-            PrintWriter pw = new FastPrintWriter(out);
-            SimpleDateFormat formatter = new SimpleDateFormat();
-            String dateString = formatter.format(new Date(System.currentTimeMillis()));
-            pw.println(dateString + ": " + msg);
-            pw.close();
-            FileUtils.setPermissions(
-                    fname.toString(),
-                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
-                    -1, -1);
-        } catch (java.io.IOException e) {
-        }
-    }
-
-    private long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
-        if (srcFile.isDirectory()) {
-            final File baseFile = new File(pkg.baseCodePath);
-            long maxModifiedTime = baseFile.lastModified();
-            if (pkg.splitCodePaths != null) {
-                for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
-                    final File splitFile = new File(pkg.splitCodePaths[i]);
-                    maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
-                }
-            }
-            return maxModifiedTime;
-        }
-        return srcFile.lastModified();
-    }
-
     private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
             final int policyFlags) throws PackageManagerException {
         // When upgrading from pre-N MR1, verify the package time stamp using the package
@@ -8385,7 +8080,7 @@
                 && !isCompatSignatureUpdateNeeded(pkg)
                 && !isRecoverSignatureUpdateNeeded(pkg)) {
             long mSigningKeySetId = ps.keySetData.getProperSigningKeySet();
-            KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
             ArraySet<PublicKey> signingKs;
             synchronized (mPackages) {
                 signingKs = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
@@ -8687,6 +8382,7 @@
                 pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString;
                 pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString;
             }
+            pkg.mExtras = updatedPkg;
 
             throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "
                     + scanFile + " ignored: updated version " + ps.versionCode
@@ -8800,7 +8496,7 @@
         return scannedPkg;
     }
 
-    private void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
+    private static void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
         // Derive the new package synthetic package name
         pkg.setPackageName(pkg.packageName + STATIC_SHARED_LIB_DELIMITER
                 + pkg.staticSharedLibVersion);
@@ -8814,49 +8510,6 @@
         return processName;
     }
 
-    private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)
-            throws PackageManagerException {
-        if (pkgSetting.signatures.mSignatures != null) {
-            // Already existing package. Make sure signatures match
-            boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
-                    == PackageManager.SIGNATURE_MATCH;
-            if (!match) {
-                match = compareSignaturesCompat(pkgSetting.signatures, pkg)
-                        == PackageManager.SIGNATURE_MATCH;
-            }
-            if (!match) {
-                match = compareSignaturesRecover(pkgSetting.signatures, pkg)
-                        == PackageManager.SIGNATURE_MATCH;
-            }
-            if (!match) {
-                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
-                        + pkg.packageName + " signatures do not match the "
-                        + "previously installed version; ignoring!");
-            }
-        }
-
-        // Check for shared user signatures
-        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
-            // Already existing package. Make sure signatures match
-            boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
-                    pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
-            if (!match) {
-                match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
-                        == PackageManager.SIGNATURE_MATCH;
-            }
-            if (!match) {
-                match = compareSignaturesRecover(pkgSetting.sharedUser.signatures, pkg)
-                        == PackageManager.SIGNATURE_MATCH;
-            }
-            if (!match) {
-                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
-                        "Package " + pkg.packageName
-                        + " has no signatures that match those in shared user "
-                        + pkgSetting.sharedUser.name + "; ignoring!");
-            }
-        }
-    }
-
     /**
      * Enforces that only the system UID or root's UID can call a method exposed
      * via Binder.
@@ -9827,24 +9480,6 @@
         return res;
     }
 
-    /**
-     * Derive the value of the {@code cpuAbiOverride} based on the provided
-     * value and an optional stored value from the package settings.
-     */
-    private static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
-        String cpuAbiOverride = null;
-
-        if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
-            cpuAbiOverride = null;
-        } else if (abiOverride != null) {
-            cpuAbiOverride = abiOverride;
-        } else if (settings != null) {
-            cpuAbiOverride = settings.cpuAbiOverrideString;
-        }
-
-        return cpuAbiOverride;
-    }
-
     private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
             final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
                     throws PackageManagerException {
@@ -9965,7 +9600,7 @@
 
         if (Build.IS_DEBUGGABLE &&
                 pkg.isPrivileged() &&
-                !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+                SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
             PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
         }
 
@@ -10192,8 +9827,9 @@
                 }
             }
 
-            if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
-                if (checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+                if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
                     // We just determined the app is signed correctly, so bring
                     // over the latest parsed certs.
                     pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -10211,8 +9847,16 @@
                 }
             } else {
                 try {
-                    // SIDE EFFECTS; compareSignaturesCompat() changes KeysetManagerService
-                    verifySignaturesLP(signatureCheckPs, pkg);
+                    final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
+                    final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+                    final boolean compatMatch = verifySignatures(signatureCheckPs, pkg.mSignatures,
+                            compareCompat, compareRecover);
+                    // The new KeySets will be re-added later in the scanning process.
+                    if (compatMatch) {
+                        synchronized (mPackages) {
+                            ksms.removeAppKeySetDataLPw(pkg.packageName);
+                        }
+                    }
                     // We just determined the app is signed correctly, so bring
                     // over the latest parsed certs.
                     pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -10500,7 +10144,7 @@
         }
 
         // Make sure we're not adding any bogus keyset info
-        KeySetManagerService ksms = mSettings.mKeySetManagerService;
+        final KeySetManagerService ksms = mSettings.mKeySetManagerService;
         ksms.assertScannedPackageValid(pkg);
 
         synchronized (mPackages) {
@@ -15656,42 +15300,6 @@
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
-    private boolean shouldCheckUpgradeKeySetLP(PackageSettingBase oldPs, int scanFlags) {
-        // Can't rotate keys during boot or if sharedUser.
-        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
-                || !oldPs.keySetData.isUsingUpgradeKeySets()) {
-            return false;
-        }
-        // app is using upgradeKeySets; make sure all are valid
-        KeySetManagerService ksms = mSettings.mKeySetManagerService;
-        long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
-        for (int i = 0; i < upgradeKeySets.length; i++) {
-            if (!ksms.isIdValidKeySetId(upgradeKeySets[i])) {
-                Slog.wtf(TAG, "Package "
-                         + (oldPs.name != null ? oldPs.name : "<null>")
-                         + " contains upgrade-key-set reference to unknown key-set: "
-                         + upgradeKeySets[i]
-                         + " reverting to signatures check.");
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private boolean checkUpgradeKeySetLP(PackageSettingBase oldPS, PackageParser.Package newPkg) {
-        // Upgrade keysets are being used.  Determine if new package has a superset of the
-        // required keys.
-        long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
-        KeySetManagerService ksms = mSettings.mKeySetManagerService;
-        for (int i = 0; i < upgradeKeySets.length; i++) {
-            Set<PublicKey> upgradeSet = ksms.getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
-            if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private static void updateDigest(MessageDigest digest, File file) throws IOException {
         try (DigestInputStream digestStream =
                 new DigestInputStream(new FileInputStream(file), digest)) {
@@ -15730,8 +15338,9 @@
             ps = mSettings.mPackages.get(pkgName);
 
             // verify signatures are valid
-            if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
-                if (!checkUpgradeKeySetLP(ps, pkg)) {
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            if (ksms.shouldCheckUpgradeKeySetLocked(ps, scanFlags)) {
+                if (!ksms.checkUpgradeKeySetLocked(ps, pkg)) {
                     res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                             "New package not signed by keys specified by upgrade-keysets: "
                                     + pkgName);
@@ -16632,8 +16241,9 @@
                 // Quick sanity check that we're signed correctly if updating;
                 // we'll check this again later when scanning, but we want to
                 // bail early here before tripping over redefined permissions.
-                if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
-                    if (!checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
+                final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+                if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+                    if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
                         res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
                                 + pkg.packageName + " upgrade keys do not match the "
                                 + "previously installed version");
@@ -16641,7 +16251,16 @@
                     }
                 } else {
                     try {
-                        verifySignaturesLP(signatureCheckPs, pkg);
+                        final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
+                        final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+                        final boolean compatMatch = verifySignatures(
+                                signatureCheckPs, pkg.mSignatures, compareCompat, compareRecover);
+                        // The new KeySets will be re-added later in the scanning process.
+                        if (compatMatch) {
+                            synchronized (mPackages) {
+                                ksms.removeAppKeySetDataLPw(pkg.packageName);
+                            }
+                        }
                     } catch (PackageManagerException e) {
                         res.setError(e.error, e.getMessage());
                         return;
@@ -16679,10 +16298,11 @@
                     final boolean sigsOk;
                     final String sourcePackageName = bp.getSourcePackageName();
                     final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
+                    final KeySetManagerService ksms = mSettings.mKeySetManagerService;
                     if (sourcePackageName.equals(pkg.packageName)
-                            && (shouldCheckUpgradeKeySetLP(sourcePackageSetting,
-                                    scanFlags))) {
-                        sigsOk = checkUpgradeKeySetLP(sourcePackageSetting, pkg);
+                            && (ksms.shouldCheckUpgradeKeySetLocked(
+                                    sourcePackageSetting, scanFlags))) {
+                        sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg);
                     } else {
                         sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
                                 pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
@@ -20282,6 +19902,23 @@
                         .getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_SYSTEM);
         co.onChange(true);
 
+        // This observer provides an one directional mapping from Global.PRIV_APP_OOB_ENABLED to
+        // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
+        // it is done.
+        ContentObserver privAppOobObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
+                SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
+                        oobEnabled == 1 ? "true" : "false");
+            }
+        };
+        mContext.getContentResolver().registerContentObserver(
+                Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
+                UserHandle.USER_SYSTEM);
+        // At boot, restore the value from the setting, which persists across reboot.
+        privAppOobObserver.onChange(true);
+
         // Disable any carrier apps. We do this very early in boot to prevent the apps from being
         // disabled after already being started.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
@@ -21028,34 +20665,11 @@
 
                 pw.println();
                 pw.println("Package warning messages:");
-                BufferedReader in = null;
-                String line = null;
-                try {
-                    in = new BufferedReader(new FileReader(getSettingsProblemFile()));
-                    while ((line = in.readLine()) != null) {
-                        if (line.contains("ignored: updated version")) continue;
-                        pw.println(line);
-                    }
-                } catch (IOException ignored) {
-                } finally {
-                    IoUtils.closeQuietly(in);
-                }
+                dumpCriticalInfo(pw, null);
             }
 
             if (checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES)) {
-                BufferedReader in = null;
-                String line = null;
-                try {
-                    in = new BufferedReader(new FileReader(getSettingsProblemFile()));
-                    while ((line = in.readLine()) != null) {
-                        if (line.contains("ignored: updated version")) continue;
-                        pw.print("msg,");
-                        pw.println(line);
-                    }
-                } catch (IOException ignored) {
-                } finally {
-                    IoUtils.closeQuietly(in);
-                }
+                dumpCriticalInfo(pw, "msg,");
             }
         }
 
@@ -21101,26 +20715,11 @@
             dumpFeaturesProto(proto);
             mSettings.dumpPackagesProto(proto);
             mSettings.dumpSharedUsersProto(proto);
-            dumpMessagesProto(proto);
+            dumpCriticalInfo(proto);
         }
         proto.flush();
     }
 
-    private void dumpMessagesProto(ProtoOutputStream proto) {
-        BufferedReader in = null;
-        String line = null;
-        try {
-            in = new BufferedReader(new FileReader(getSettingsProblemFile()));
-            while ((line = in.readLine()) != null) {
-                if (line.contains("ignored: updated version")) continue;
-                proto.write(PackageServiceDumpProto.MESSAGES, line);
-            }
-        } catch (IOException ignored) {
-        } finally {
-            IoUtils.closeQuietly(in);
-        }
-    }
-
     private void dumpFeaturesProto(ProtoOutputStream proto) {
         synchronized (mAvailableFeatures) {
             final int count = mAvailableFeatures.size();
@@ -22529,7 +22128,7 @@
                 Slog.w(TAG, "KeySet requested for filtered package: " + packageName);
                 throw new IllegalArgumentException("Unknown package: " + packageName);
             }
-            KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
             return new KeySet(ksms.getKeySetByAliasAndPackageNameLPr(packageName, alias));
         }
     }
@@ -22558,7 +22157,7 @@
                     && Process.SYSTEM_UID != callingUid) {
                 throw new SecurityException("May not access signing KeySet of other apps.");
             }
-            KeySetManagerService ksms = mSettings.mKeySetManagerService;
+            final KeySetManagerService ksms = mSettings.mKeySetManagerService;
             return new KeySet(ksms.getSigningKeySetByPackageNameLPr(packageName));
         }
     }
@@ -22582,7 +22181,7 @@
             }
             IBinder ksh = ks.getToken();
             if (ksh instanceof KeySetHandle) {
-                KeySetManagerService ksms = mSettings.mKeySetManagerService;
+                final KeySetManagerService ksms = mSettings.mKeySetManagerService;
                 return ksms.packageIsSignedByLPr(packageName, (KeySetHandle) ksh);
             }
             return false;
@@ -22608,7 +22207,7 @@
             }
             IBinder ksh = ks.getToken();
             if (ksh instanceof KeySetHandle) {
-                KeySetManagerService ksms = mSettings.mKeySetManagerService;
+                final KeySetManagerService ksms = mSettings.mKeySetManagerService;
                 return ksms.packageIsSignedByExactlyLPr(packageName, (KeySetHandle) ksh);
             }
             return false;
diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java
index 67e06dd..758abd7 100644
--- a/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/com/android/server/pm/PackageManagerServiceUtils.java
@@ -16,42 +16,74 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
+import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
+import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
+import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
+import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.server.EventLogTags;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.PackageDexUsage;
 
-import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
-import static com.android.server.pm.PackageManagerService.TAG;
-
-import com.android.internal.util.ArrayUtils;
-
 import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
 import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
 import android.os.Build;
 import android.os.Debug;
+import android.os.Environment;
+import android.os.FileUtils;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.service.pm.PackageServiceDumpProto;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 import android.util.jar.StrictJarFile;
-import dalvik.system.VMRuntime;
-import libcore.io.Libcore;
+import android.util.proto.ProtoOutputStream;
 
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import libcore.io.Streams;
+
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FilenameFilter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.function.Predicate;
+import java.util.zip.GZIPInputStream;
 import java.util.zip.ZipEntry;
 
 /**
@@ -200,7 +232,7 @@
      *
      * If it doesn't have sufficient information about the package, it return <code>false</code>.
      */
-    static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
+    public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
             long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
             long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
 
@@ -263,6 +295,21 @@
         return false;
     }
 
+    public static long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
+        if (srcFile.isDirectory()) {
+            final File baseFile = new File(pkg.baseCodePath);
+            long maxModifiedTime = baseFile.lastModified();
+            if (pkg.splitCodePaths != null) {
+                for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
+                    final File splitFile = new File(pkg.splitCodePaths[i]);
+                    maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
+                }
+            }
+            return maxModifiedTime;
+        }
+        return srcFile.lastModified();
+    }
+
     /**
      * Checks that the archive located at {@code fileName} has uncompressed dex file and so
      * files that can be direclty mapped.
@@ -318,6 +365,57 @@
         }
     }
 
+    private static File getSettingsProblemFile() {
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        File fname = new File(systemDir, "uiderrors.txt");
+        return fname;
+    }
+
+    public static void dumpCriticalInfo(ProtoOutputStream proto) {
+        try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
+            String line = null;
+            while ((line = in.readLine()) != null) {
+                if (line.contains("ignored: updated version")) continue;
+                proto.write(PackageServiceDumpProto.MESSAGES, line);
+            }
+        } catch (IOException ignored) {
+        }
+    }
+
+    public static void dumpCriticalInfo(PrintWriter pw, String msg) {
+        try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
+            String line = null;
+            while ((line = in.readLine()) != null) {
+                if (line.contains("ignored: updated version")) continue;
+                if (msg != null) {
+                    pw.print(msg);
+                }
+                pw.println(line);
+            }
+        } catch (IOException ignored) {
+        }
+    }
+
+    public static void logCriticalInfo(int priority, String msg) {
+        Slog.println(priority, TAG, msg);
+        EventLogTags.writePmCriticalInfo(msg);
+        try {
+            File fname = getSettingsProblemFile();
+            FileOutputStream out = new FileOutputStream(fname, true);
+            PrintWriter pw = new FastPrintWriter(out);
+            SimpleDateFormat formatter = new SimpleDateFormat();
+            String dateString = formatter.format(new Date(System.currentTimeMillis()));
+            pw.println(dateString + ": " + msg);
+            pw.close();
+            FileUtils.setPermissions(
+                    fname.toString(),
+                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
+                    -1, -1);
+        } catch (java.io.IOException e) {
+        }
+    }
+
     public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
         if (callingUid == Process.SHELL_UID) {
             if (userHandle >= 0
@@ -331,4 +429,240 @@
             }
         }
     }
+
+    /**
+     * Derive the value of the {@code cpuAbiOverride} based on the provided
+     * value and an optional stored value from the package settings.
+     */
+    public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
+        String cpuAbiOverride = null;
+        if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
+            cpuAbiOverride = null;
+        } else if (abiOverride != null) {
+            cpuAbiOverride = abiOverride;
+        } else if (settings != null) {
+            cpuAbiOverride = settings.cpuAbiOverrideString;
+        }
+        return cpuAbiOverride;
+    }
+
+    /**
+     * Compares two sets of signatures. Returns:
+     * <br />
+     * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
+     * <br />
+     * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
+     * <br />
+     * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
+     * <br />
+     * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
+     * <br />
+     * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
+     */
+    public static int compareSignatures(Signature[] s1, Signature[] s2) {
+        if (s1 == null) {
+            return s2 == null
+                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
+                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
+        }
+
+        if (s2 == null) {
+            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
+        }
+
+        if (s1.length != s2.length) {
+            return PackageManager.SIGNATURE_NO_MATCH;
+        }
+
+        // Since both signature sets are of size 1, we can compare without HashSets.
+        if (s1.length == 1) {
+            return s1[0].equals(s2[0]) ?
+                    PackageManager.SIGNATURE_MATCH :
+                    PackageManager.SIGNATURE_NO_MATCH;
+        }
+
+        ArraySet<Signature> set1 = new ArraySet<Signature>();
+        for (Signature sig : s1) {
+            set1.add(sig);
+        }
+        ArraySet<Signature> set2 = new ArraySet<Signature>();
+        for (Signature sig : s2) {
+            set2.add(sig);
+        }
+        // Make sure s2 contains all signatures in s1.
+        if (set1.equals(set2)) {
+            return PackageManager.SIGNATURE_MATCH;
+        }
+        return PackageManager.SIGNATURE_NO_MATCH;
+    }
+
+    /**
+     * Used for backward compatibility to make sure any packages with
+     * certificate chains get upgraded to the new style. {@code existingSigs}
+     * will be in the old format (since they were stored on disk from before the
+     * system upgrade) and {@code scannedSigs} will be in the newer format.
+     */
+    private static boolean matchSignaturesCompat(String packageName,
+            PackageSignatures packageSignatures, Signature[] parsedSignatures) {
+        ArraySet<Signature> existingSet = new ArraySet<Signature>();
+        for (Signature sig : packageSignatures.mSignatures) {
+            existingSet.add(sig);
+        }
+        ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
+        for (Signature sig : parsedSignatures) {
+            try {
+                Signature[] chainSignatures = sig.getChainSignatures();
+                for (Signature chainSig : chainSignatures) {
+                    scannedCompatSet.add(chainSig);
+                }
+            } catch (CertificateEncodingException e) {
+                scannedCompatSet.add(sig);
+            }
+        }
+        // make sure the expanded scanned set contains all signatures in the existing one
+        if (scannedCompatSet.equals(existingSet)) {
+            // migrate the old signatures to the new scheme
+            packageSignatures.assignSignatures(parsedSignatures);
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean matchSignaturesRecover(String packageName,
+            Signature[] existingSignatures, Signature[] parsedSignatures) {
+        String msg = null;
+        try {
+            if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) {
+                logCriticalInfo(Log.INFO,
+                        "Recovered effectively matching certificates for " + packageName);
+                return true;
+            }
+        } catch (CertificateException e) {
+            msg = e.getMessage();
+        }
+        logCriticalInfo(Log.INFO,
+                "Failed to recover certificates for " + packageName + ": " + msg);
+        return false;
+    }
+
+    /**
+     * Verifies that signatures match.
+     * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
+     * @throws PackageManagerException if the signatures did not match.
+     */
+    public static boolean verifySignatures(PackageSetting pkgSetting,
+            Signature[] parsedSignatures, boolean compareCompat, boolean compareRecover)
+            throws PackageManagerException {
+        final String packageName = pkgSetting.name;
+        boolean compatMatch = false;
+        if (pkgSetting.signatures.mSignatures != null) {
+            // Already existing package. Make sure signatures match
+            boolean match = compareSignatures(pkgSetting.signatures.mSignatures, parsedSignatures)
+                    == PackageManager.SIGNATURE_MATCH;
+            if (!match && compareCompat) {
+                match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures);
+                compatMatch = match;
+            }
+            if (!match && compareRecover) {
+                match = matchSignaturesRecover(
+                        packageName, pkgSetting.signatures.mSignatures, parsedSignatures);
+            }
+            if (!match) {
+                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                        "Package " + packageName +
+                        " signatures don't match previously installed version; ignoring!");
+            }
+        }
+        // Check for shared user signatures
+        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
+            // Already existing package. Make sure signatures match
+            boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
+                    parsedSignatures) == PackageManager.SIGNATURE_MATCH;
+            if (!match) {
+                match = matchSignaturesCompat(
+                        packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
+            }
+            if (!match && compareCompat) {
+                match = matchSignaturesRecover(
+                        packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
+                compatMatch |= match;
+            }
+            if (!match && compareRecover) {
+                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+                        "Package " + packageName
+                        + " has no signatures that match those in shared user "
+                        + pkgSetting.sharedUser.name + "; ignoring!");
+            }
+        }
+        return compatMatch;
+    }
+
+    public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
+        if (DEBUG_COMPRESSION) {
+            Slog.i(TAG, "Decompress file"
+                    + "; src: " + srcFile.getAbsolutePath()
+                    + ", dst: " + dstFile.getAbsolutePath());
+        }
+        try (
+                InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
+                OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
+        ) {
+            Streams.copy(fileIn, fileOut);
+            Os.chmod(dstFile.getAbsolutePath(), 0644);
+            return PackageManager.INSTALL_SUCCEEDED;
+        } catch (IOException e) {
+            logCriticalInfo(Log.ERROR, "Failed to decompress file"
+                    + "; src: " + srcFile.getAbsolutePath()
+                    + ", dst: " + dstFile.getAbsolutePath());
+        }
+        return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+    }
+
+    public static File[] getCompressedFiles(String codePath) {
+        final File stubCodePath = new File(codePath);
+        final String stubName = stubCodePath.getName();
+
+        // The layout of a compressed package on a given partition is as follows :
+        //
+        // Compressed artifacts:
+        //
+        // /partition/ModuleName/foo.gz
+        // /partation/ModuleName/bar.gz
+        //
+        // Stub artifact:
+        //
+        // /partition/ModuleName-Stub/ModuleName-Stub.apk
+        //
+        // In other words, stub is on the same partition as the compressed artifacts
+        // and in a directory that's suffixed with "-Stub".
+        int idx = stubName.lastIndexOf(STUB_SUFFIX);
+        if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
+            return null;
+        }
+
+        final File stubParentDir = stubCodePath.getParentFile();
+        if (stubParentDir == null) {
+            Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
+            return null;
+        }
+
+        final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
+        final File[] files = compressedPath.listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String name) {
+                return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
+            }
+        });
+
+        if (DEBUG_COMPRESSION && files != null && files.length > 0) {
+            Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
+        }
+
+        return files;
+    }
+
+    public static boolean compressedFileExists(String codePath) {
+        final File[] compressedFiles = getCompressedFiles(codePath);
+        return compressedFiles != null && compressedFiles.length > 0;
+    }
 }
diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java
index a2099e6..807eb1a 100644
--- a/com/android/server/pm/PackageManagerShellCommand.java
+++ b/com/android/server/pm/PackageManagerShellCommand.java
@@ -54,6 +54,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IUserManager;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -78,7 +79,6 @@
 import libcore.io.IoUtils;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -102,8 +102,6 @@
 class PackageManagerShellCommand extends ShellCommand {
     /** Path for streaming APK content */
     private static final String STDIN_PATH = "-";
-    /** Whether or not APK content must be streamed from stdin */
-    private static final boolean FORCE_STREAM_INSTALL = true;
 
     final IPackageManager mInterface;
     final private WeakHashMap<String, Resources> mResourceCache =
@@ -255,30 +253,27 @@
     }
 
     private void setParamsSize(InstallParams params, String inPath) {
-        // If we're forced to stream the package, the params size
-        // must be set via command-line argument. There's nothing
-        // to do here.
-        if (FORCE_STREAM_INSTALL) {
-            return;
-        }
-        final PrintWriter pw = getOutPrintWriter();
         if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
-            File file = new File(inPath);
-            if (file.isFile()) {
+            final ParcelFileDescriptor fd = openFileForSystem(inPath, "r");
+            if (fd == null) {
+                getErrPrintWriter().println("Error: Can't open file: " + inPath);
+                throw new IllegalArgumentException("Error: Can't open file: " + inPath);
+            }
+            try {
+                ApkLite baseApk = PackageParser.parseApkLite(fd.getFileDescriptor(), inPath, 0);
+                PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
+                        null, null);
+                params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
+                        pkgLite, params.sessionParams.abiOverride));
+            } catch (PackageParserException | IOException e) {
+                getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
+                throw new IllegalArgumentException(
+                        "Error: Failed to parse APK file: " + inPath, e);
+            } finally {
                 try {
-                    ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
-                            null, null);
-                    params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
-                            pkgLite, params.sessionParams.abiOverride));
-                } catch (PackageParserException | IOException e) {
-                    pw.println("Error: Failed to parse APK file: " + file);
-                    throw new IllegalArgumentException(
-                            "Error: Failed to parse APK file: " + file, e);
+                    fd.close();
+                } catch (IOException e) {
                 }
-            } else {
-                pw.println("Error: Can't open non-file: " + inPath);
-                throw new IllegalArgumentException("Error: Can't open non-file: " + inPath);
             }
         }
     }
@@ -1914,6 +1909,12 @@
                         throw new IllegalArgumentException("Missing inherit package name");
                     }
                     break;
+                case "--pkg":
+                    sessionParams.appPackageName = getNextArg();
+                    if (sessionParams.appPackageName == null) {
+                        throw new IllegalArgumentException("Missing package name");
+                    }
+                    break;
                 case "-S":
                     final long sizeBytes = Long.parseLong(getNextArg());
                     if (sizeBytes <= 0) {
@@ -1925,6 +1926,7 @@
                     sessionParams.abiOverride = checkAbiArgument(getNextArg());
                     break;
                 case "--ephemeral":
+                case "--instant":
                 case "--instantapp":
                     sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
                     break;
@@ -2092,20 +2094,24 @@
     private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
             boolean logSuccess) throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        if (FORCE_STREAM_INSTALL && inPath != null && !STDIN_PATH.equals(inPath)) {
-            pw.println("Error: APK content must be streamed");
-            return 1;
-        }
+        final ParcelFileDescriptor fd;
         if (STDIN_PATH.equals(inPath)) {
-            inPath = null;
+            fd = null;
         } else if (inPath != null) {
-            final File file = new File(inPath);
-            if (file.isFile()) {
-                sizeBytes = file.length();
+            fd = openFileForSystem(inPath, "r");
+            if (fd == null) {
+                return -1;
             }
+            sizeBytes = fd.getStatSize();
+            if (sizeBytes < 0) {
+                getErrPrintWriter().println("Unable to get size of: " + inPath);
+                return -1;
+            }
+        } else {
+            fd = null;
         }
         if (sizeBytes <= 0) {
-            pw.println("Error: must specify a APK size");
+            getErrPrintWriter().println("Error: must specify a APK size");
             return 1;
         }
 
@@ -2118,8 +2124,8 @@
             session = new PackageInstaller.Session(
                     mInterface.getPackageInstaller().openSession(sessionId));
 
-            if (inPath != null) {
-                in = new FileInputStream(inPath);
+            if (fd != null) {
+                in = new ParcelFileDescriptor.AutoCloseInputStream(fd);
             } else {
                 in = new SizedInputStream(getRawInputStream(), sizeBytes);
             }
@@ -2144,7 +2150,7 @@
             }
             return 0;
         } catch (IOException e) {
-            pw.println("Error: failed to write; " + e.getMessage());
+            getErrPrintWriter().println("Error: failed to write; " + e.getMessage());
             return 1;
         } finally {
             IoUtils.closeQuietly(out);
diff --git a/com/android/server/pm/ShortcutPackage.java b/com/android/server/pm/ShortcutPackage.java
index a3585bc..ba97c42 100644
--- a/com/android/server/pm/ShortcutPackage.java
+++ b/com/android/server/pm/ShortcutPackage.java
@@ -615,10 +615,16 @@
 
             // Fix up isPinned for the caller.  Note we need to do it before the "test" callback,
             // since it may check isPinned.
-            if (!isPinnedByCaller) {
-                clone.clearFlags(ShortcutInfo.FLAG_PINNED);
+            // However, if getPinnedByAnyLauncher is set, we do it after the test.
+            if (!getPinnedByAnyLauncher) {
+                if (!isPinnedByCaller) {
+                    clone.clearFlags(ShortcutInfo.FLAG_PINNED);
+                }
             }
             if (query == null || query.test(clone)) {
+                if (!isPinnedByCaller) {
+                    clone.clearFlags(ShortcutInfo.FLAG_PINNED);
+                }
                 result.add(clone);
             }
         }
diff --git a/com/android/server/pm/ShortcutService.java b/com/android/server/pm/ShortcutService.java
index 1c002aa..0907dd7 100644
--- a/com/android/server/pm/ShortcutService.java
+++ b/com/android/server/pm/ShortcutService.java
@@ -2234,7 +2234,7 @@
     // We override this method in unit tests to do a simpler check.
     boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId,
             int callingPid, int callingUid) {
-        if (injectCheckAccessShortcutsPermission(callingPid, callingUid)) {
+        if (canSeeAnyPinnedShortcut(callingPackage, userId, callingPid, callingUid)) {
             return true;
         }
         final long start = injectElapsedRealtime();
@@ -2245,10 +2245,21 @@
         }
     }
 
+    boolean canSeeAnyPinnedShortcut(@NonNull String callingPackage, int userId,
+            int callingPid, int callingUid) {
+        if (injectHasAccessShortcutsPermission(callingPid, callingUid)) {
+            return true;
+        }
+        synchronized (mLock) {
+            return getUserShortcutsLocked(userId).hasHostPackage(callingPackage);
+        }
+    }
+
     /**
      * Returns true if the caller has the "ACCESS_SHORTCUTS" permission.
      */
-    boolean injectCheckAccessShortcutsPermission(int callingPid, int callingUid) {
+    @VisibleForTesting
+    boolean injectHasAccessShortcutsPermission(int callingPid, int callingUid) {
         return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS,
                 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
     }
@@ -2361,6 +2372,16 @@
         }
     }
 
+    public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
+            int userId) {
+        synchronized (mLock) {
+            throwIfUserLockedL(userId);
+
+            final ShortcutUser user = getUserShortcutsLocked(userId);
+            user.setShortcutHostPackage(type, packageName);
+        }
+    }
+
     // === House keeping ===
 
     private void cleanUpPackageForAllLoadedUsers(String packageName, @UserIdInt int packageUserId,
@@ -2477,8 +2498,8 @@
             final ArraySet<String> ids = shortcutIds == null ? null
                     : new ArraySet<>(shortcutIds);
 
-            final ShortcutPackage p = getUserShortcutsLocked(userId)
-                    .getPackageShortcutsIfExists(packageName);
+            final ShortcutUser user = getUserShortcutsLocked(userId);
+            final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName);
             if (p == null) {
                 return; // No need to instantiate ShortcutPackage.
             }
@@ -2486,9 +2507,12 @@
             final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
             final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
 
+            final boolean canAccessAllShortcuts =
+                    canSeeAnyPinnedShortcut(callingPackage, launcherUserId, callingPid, callingUid);
+
             final boolean getPinnedByAnyLauncher =
-                    ((queryFlags & ShortcutQuery.FLAG_MATCH_ALL_PINNED) != 0)
-                    && injectCheckAccessShortcutsPermission(callingPid, callingUid);
+                    canAccessAllShortcuts &&
+                    ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER) != 0);
 
             p.findAll(ret,
                     (ShortcutInfo si) -> {
@@ -2507,7 +2531,7 @@
                         if (matchDynamic && si.isDynamic()) {
                             return true;
                         }
-                        if ((matchPinned && si.isPinned()) || getPinnedByAnyLauncher) {
+                        if ((matchPinned || getPinnedByAnyLauncher) && si.isPinned()) {
                             return true;
                         }
                         if (matchManifest && si.isDeclaredInManifest()) {
@@ -2600,14 +2624,15 @@
                         .attemptToRestoreIfNeededAndSave();
 
                 final boolean getPinnedByAnyLauncher =
-                        injectCheckAccessShortcutsPermission(callingPid, callingUid);
+                        canSeeAnyPinnedShortcut(callingPackage, launcherUserId,
+                                callingPid, callingUid);
 
                 // Make sure the shortcut is actually visible to the launcher.
                 final ShortcutInfo si = getShortcutInfoLocked(
                         launcherUserId, callingPackage, packageName, shortcutId, userId,
                         getPinnedByAnyLauncher);
                 // "si == null" should suffice here, but check the flags too just to make sure.
-                if (si == null || !si.isEnabled() || !si.isAlive()) {
+                if (si == null || !si.isEnabled() || !(si.isAlive() || getPinnedByAnyLauncher)) {
                     Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled");
                     return null;
                 }
@@ -2697,6 +2722,12 @@
         }
 
         @Override
+        public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
+                int userId) {
+            ShortcutService.this.setShortcutHostPackage(type, packageName, userId);
+        }
+
+        @Override
         public boolean requestPinAppWidget(@NonNull String callingPackage,
                 @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
                 @Nullable IntentSender resultIntent, int userId) {
diff --git a/com/android/server/pm/ShortcutUser.java b/com/android/server/pm/ShortcutUser.java
index 48eccd0..1efd765 100644
--- a/com/android/server/pm/ShortcutUser.java
+++ b/com/android/server/pm/ShortcutUser.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 import android.text.format.Formatter;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
@@ -123,6 +124,20 @@
     /** In-memory-cached default launcher. */
     private ComponentName mCachedLauncher;
 
+    /**
+     * Keep track of additional packages that other parts of the system have said are
+     * allowed to access shortcuts.  The key is the part of the system it came from,
+     * the value is the package name that has access.  We don't persist these because
+     * at boot all relevant system services will push this data back to us they do their
+     * normal evaluation of the state of the world.
+     */
+    private final ArrayMap<String, String> mHostPackages = new ArrayMap<>();
+
+    /**
+     * Set of package name values from above.
+     */
+    private final ArraySet<String> mHostPackageSet = new ArraySet<>();
+
     private String mKnownLocales;
 
     private long mLastAppScanTime;
@@ -467,6 +482,23 @@
         return mCachedLauncher;
     }
 
+    public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName) {
+        if (packageName != null) {
+            mHostPackages.put(type, packageName);
+        } else {
+            mHostPackages.remove(type);
+        }
+
+        mHostPackageSet.clear();
+        for (int i = 0; i < mHostPackages.size(); i++) {
+            mHostPackageSet.add(mHostPackages.valueAt(i));
+        }
+    }
+
+    public boolean hasHostPackage(@NonNull String packageName) {
+        return mHostPackageSet.contains(packageName);
+    }
+
     public void resetThrottling() {
         for (int i = mPackages.size() - 1; i >= 0; i--) {
             mPackages.valueAt(i).resetThrottling();
@@ -555,6 +587,18 @@
             pw.print("Last known launcher: ");
             pw.print(mLastKnownLauncher);
             pw.println();
+
+            if (mHostPackages.size() > 0) {
+                pw.print(prefix);
+                pw.println("Host packages:");
+                for (int i = 0; i < mHostPackages.size(); i++) {
+                    pw.print(prefix);
+                    pw.print("  ");
+                    pw.print(mHostPackages.keyAt(i));
+                    pw.print(": ");
+                    pw.println(mHostPackages.valueAt(i));
+                }
+            }
         }
 
         for (int i = 0; i < mLaunchers.size(); i++) {
diff --git a/com/android/server/pm/UserDataPreparer.java b/com/android/server/pm/UserDataPreparer.java
index bfe09b8..96c102b 100644
--- a/com/android/server/pm/UserDataPreparer.java
+++ b/com/android/server/pm/UserDataPreparer.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Environment;
@@ -40,8 +42,6 @@
 import java.util.Objects;
 import java.util.Set;
 
-import static com.android.server.pm.PackageManagerService.logCriticalInfo;
-
 /**
  * Helper class for preparing and destroying user storage
  */
diff --git a/com/android/server/pm/UserManagerService.java b/com/android/server/pm/UserManagerService.java
index 1e5245c..1152310 100644
--- a/com/android/server/pm/UserManagerService.java
+++ b/com/android/server/pm/UserManagerService.java
@@ -1010,6 +1010,23 @@
         }
     }
 
+    @Override
+    public boolean hasRestrictedProfiles() {
+        checkManageUsersPermission("hasRestrictedProfiles");
+        final int callingUserId = UserHandle.getCallingUserId();
+        synchronized (mUsersLock) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
+                UserInfo profile = mUsers.valueAt(i).info;
+                if (callingUserId != profile.id
+                        && profile.restrictedProfileParentId == callingUserId) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
     /*
      * Should be locked on mUsers before calling this.
      */
diff --git a/com/android/server/pm/UserRestrictionsUtils.java b/com/android/server/pm/UserRestrictionsUtils.java
index c86122f..4b13404 100644
--- a/com/android/server/pm/UserRestrictionsUtils.java
+++ b/com/android/server/pm/UserRestrictionsUtils.java
@@ -66,6 +66,7 @@
 
     public static final Set<String> USER_RESTRICTIONS = newSetWithUniqueCheck(new String[] {
             UserManager.DISALLOW_CONFIG_WIFI,
+            UserManager.DISALLOW_CONFIG_LOCALE,
             UserManager.DISALLOW_MODIFY_ACCOUNTS,
             UserManager.DISALLOW_INSTALL_APPS,
             UserManager.DISALLOW_UNINSTALL_APPS,
@@ -112,6 +113,7 @@
             UserManager.DISALLOW_OEM_UNLOCK,
             UserManager.DISALLOW_UNMUTE_DEVICE,
             UserManager.DISALLOW_AUTOFILL,
+            UserManager.DISALLOW_USER_SWITCH
     });
 
     /**
@@ -143,6 +145,13 @@
     );
 
     /**
+     * User restrictions that cannot be set by profile owners. Applied to all users.
+     */
+    private static final Set<String> DEVICE_OWNER_ONLY_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_USER_SWITCH
+    );
+
+    /**
      * User restrictions that can't be changed by device owner or profile owner.
      */
     private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet(
@@ -311,6 +320,7 @@
      */
     public static boolean canProfileOwnerChange(String restriction, int userId) {
         return !IMMUTABLE_BY_OWNERS.contains(restriction)
+                && !DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction)
                 && !(userId != UserHandle.USER_SYSTEM
                     && PRIMARY_USER_ONLY_RESTRICTIONS.contains(restriction));
     }
@@ -363,8 +373,9 @@
      */
     private static boolean isGlobal(boolean isDeviceOwner, String key) {
         return (isDeviceOwner &&
-                (PRIMARY_USER_ONLY_RESTRICTIONS.contains(key)|| GLOBAL_RESTRICTIONS.contains(key)))
-                || PROFILE_GLOBAL_RESTRICTIONS.contains(key);
+                (PRIMARY_USER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key)))
+                || PROFILE_GLOBAL_RESTRICTIONS.contains(key)
+                || DEVICE_OWNER_ONLY_RESTRICTIONS.contains(key);
     }
 
     /**
diff --git a/com/android/server/pm/crossprofile/CrossProfileAppsService.java b/com/android/server/pm/crossprofile/CrossProfileAppsService.java
new file mode 100644
index 0000000..0913269
--- /dev/null
+++ b/com/android/server/pm/crossprofile/CrossProfileAppsService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm.crossprofile;
+
+import android.content.Context;
+
+import com.android.server.SystemService;
+
+public class CrossProfileAppsService extends SystemService {
+    private CrossProfileAppsServiceImpl mServiceImpl;
+
+    public CrossProfileAppsService(Context context) {
+        super(context);
+        mServiceImpl = new CrossProfileAppsServiceImpl(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.CROSS_PROFILE_APPS_SERVICE, mServiceImpl);
+    }
+}
diff --git a/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
new file mode 100644
index 0000000..854b704
--- /dev/null
+++ b/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm.crossprofile;
+
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.crossprofile.ICrossProfileApps;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
+    private static final String TAG = "CrossProfileAppsService";
+
+    private Context mContext;
+    private Injector mInjector;
+
+    public CrossProfileAppsServiceImpl(Context context) {
+        this(context, new InjectorImpl(context));
+    }
+
+    @VisibleForTesting
+    CrossProfileAppsServiceImpl(Context context, Injector injector) {
+        mContext = context;
+        mInjector = injector;
+    }
+
+    @Override
+    public List<UserHandle> getTargetUserProfiles(String callingPackage) {
+        Preconditions.checkNotNull(callingPackage);
+
+        verifyCallingPackage(callingPackage);
+
+        return getTargetUserProfilesUnchecked(
+                callingPackage, mInjector.getCallingUserId());
+    }
+
+    @Override
+    public void startActivityAsUser(
+            String callingPackage,
+            ComponentName component,
+            Rect sourceBounds,
+            Bundle startActivityOptions,
+            UserHandle user) throws RemoteException {
+        Preconditions.checkNotNull(callingPackage);
+        Preconditions.checkNotNull(component);
+        Preconditions.checkNotNull(user);
+
+        verifyCallingPackage(callingPackage);
+
+        List<UserHandle> allowedTargetUsers = getTargetUserProfilesUnchecked(
+                callingPackage, mInjector.getCallingUserId());
+        if (!allowedTargetUsers.contains(user)) {
+            throw new SecurityException(
+                    callingPackage + " cannot access unrelated user " + user.getIdentifier());
+        }
+
+        // Verify that caller package is starting activity in its own package.
+        if (!callingPackage.equals(component.getPackageName())) {
+            throw new SecurityException(
+                    callingPackage + " attempts to start an activity in other package - "
+                            + component.getPackageName());
+        }
+
+        final int callingUid = mInjector.getCallingUid();
+
+        // Verify that target activity does handle the intent with ACTION_MAIN and
+        // CATEGORY_LAUNCHER as calling startActivityAsUser ignore them if component is present.
+        final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
+        launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        launchIntent.setSourceBounds(sourceBounds);
+        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        // Only package name is set here, as opposed to component name, because intent action and
+        // category are ignored if component name is present while we are resolving intent.
+        launchIntent.setPackage(component.getPackageName());
+        verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, user);
+
+        final long ident = mInjector.clearCallingIdentity();
+        try {
+            launchIntent.setComponent(component);
+            mContext.startActivityAsUser(launchIntent, startActivityOptions, user);
+        } finally {
+            mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
+    private List<UserHandle> getTargetUserProfilesUnchecked(
+            String callingPackage, @UserIdInt int callingUserId) {
+        final long ident = mInjector.clearCallingIdentity();
+        try {
+            final int[] enabledProfileIds =
+                    mInjector.getUserManager().getEnabledProfileIds(callingUserId);
+
+            List<UserHandle> targetProfiles = new ArrayList<>();
+            for (final int userId : enabledProfileIds) {
+                if (userId == callingUserId) {
+                    continue;
+                }
+                if (!isPackageEnabled(callingPackage, userId)) {
+                    continue;
+                }
+                targetProfiles.add(UserHandle.of(userId));
+            }
+            return targetProfiles;
+        } finally {
+            mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
+    private boolean isPackageEnabled(String packageName, @UserIdInt int userId) {
+        final int callingUid = mInjector.getCallingUid();
+        final long ident = mInjector.clearCallingIdentity();
+        try {
+            final PackageInfo info = mInjector.getPackageManagerInternal()
+                    .getPackageInfo(
+                            packageName,
+                            MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                            callingUid,
+                            userId);
+            return info != null && info.applicationInfo.enabled;
+        } finally {
+            mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Verify that the specified intent does resolved to the specified component and the resolved
+     * activity is exported.
+     */
+    private void verifyActivityCanHandleIntentAndExported(
+            Intent launchIntent, ComponentName component, int callingUid, UserHandle user) {
+        final long ident = mInjector.clearCallingIdentity();
+        try {
+            final List<ResolveInfo> apps =
+                    mInjector.getPackageManagerInternal().queryIntentActivities(
+                            launchIntent,
+                            MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+                            callingUid,
+                            user.getIdentifier());
+            final int size = apps.size();
+            for (int i = 0; i < size; ++i) {
+                final ActivityInfo activityInfo = apps.get(i).activityInfo;
+                if (TextUtils.equals(activityInfo.packageName, component.getPackageName())
+                        && TextUtils.equals(activityInfo.name, component.getClassName())
+                        && activityInfo.exported) {
+                    return;
+                }
+            }
+            throw new SecurityException("Attempt to launch activity without "
+                    + " category Intent.CATEGORY_LAUNCHER or activity is not exported" + component);
+        } finally {
+            mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Verify that the given calling package is belong to the calling UID.
+     */
+    private void verifyCallingPackage(String callingPackage) {
+        mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage);
+    }
+
+    private static class InjectorImpl implements Injector {
+        private Context mContext;
+
+        public InjectorImpl(Context context) {
+            mContext = context;
+        }
+
+        public int getCallingUid() {
+            return Binder.getCallingUid();
+        }
+
+        public int getCallingUserId() {
+            return UserHandle.getCallingUserId();
+        }
+
+        public UserHandle getCallingUserHandle() {
+            return Binder.getCallingUserHandle();
+        }
+
+        public long clearCallingIdentity() {
+            return Binder.clearCallingIdentity();
+        }
+
+        public void restoreCallingIdentity(long token) {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        public UserManager getUserManager() {
+            return mContext.getSystemService(UserManager.class);
+        }
+
+        public PackageManagerInternal getPackageManagerInternal() {
+            return LocalServices.getService(PackageManagerInternal.class);
+        }
+
+        public PackageManager getPackageManager() {
+            return mContext.getPackageManager();
+        }
+
+        public AppOpsManager getAppOpsManager() {
+            return mContext.getSystemService(AppOpsManager.class);
+        }
+    }
+
+    @VisibleForTesting
+    public interface Injector {
+        int getCallingUid();
+
+        int getCallingUserId();
+
+        UserHandle getCallingUserHandle();
+
+        long clearCallingIdentity();
+
+        void restoreCallingIdentity(long token);
+
+        UserManager getUserManager();
+
+        PackageManagerInternal getPackageManagerInternal();
+
+        PackageManager getPackageManager();
+
+        AppOpsManager getAppOpsManager();
+
+    }
+}
diff --git a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 533b261..c40d1fa 100644
--- a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm.permission;
 
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -383,6 +385,7 @@
                 MediaStore.AUTHORITY, userId);
         if (mediaStorePackage != null) {
             grantRuntimePermissions(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
+            grantRuntimePermissions(mediaStorePackage, PHONE_PERMISSIONS, true, userId);
         }
 
         // Downloads provider
@@ -1109,8 +1112,8 @@
         final String systemPackageName = mServiceInternal.getKnownPackageName(
                 PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
         final PackageParser.Package systemPackage = getPackage(systemPackageName);
-        return PackageManagerService.compareSignatures(systemPackage.mSignatures,
-                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+        return compareSignatures(systemPackage.mSignatures, pkg.mSignatures)
+                == PackageManager.SIGNATURE_MATCH;
     }
 
     private void grantDefaultPermissionExceptions(int userId) {
diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java
index 76805ce..7d8e206 100644
--- a/com/android/server/pm/permission/PermissionManagerService.java
+++ b/com/android/server/pm/permission/PermissionManagerService.java
@@ -37,6 +37,7 @@
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.PackageParser.Package;
+import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -92,36 +93,6 @@
 public class PermissionManagerService {
     private static final String TAG = "PackageManager";
 
-    /** All dangerous permission names in the same order as the events in MetricsEvent */
-    private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
-            Manifest.permission.READ_CALENDAR,
-            Manifest.permission.WRITE_CALENDAR,
-            Manifest.permission.CAMERA,
-            Manifest.permission.READ_CONTACTS,
-            Manifest.permission.WRITE_CONTACTS,
-            Manifest.permission.GET_ACCOUNTS,
-            Manifest.permission.ACCESS_FINE_LOCATION,
-            Manifest.permission.ACCESS_COARSE_LOCATION,
-            Manifest.permission.RECORD_AUDIO,
-            Manifest.permission.READ_PHONE_STATE,
-            Manifest.permission.CALL_PHONE,
-            Manifest.permission.READ_CALL_LOG,
-            Manifest.permission.WRITE_CALL_LOG,
-            Manifest.permission.ADD_VOICEMAIL,
-            Manifest.permission.USE_SIP,
-            Manifest.permission.PROCESS_OUTGOING_CALLS,
-            Manifest.permission.READ_CELL_BROADCASTS,
-            Manifest.permission.BODY_SENSORS,
-            Manifest.permission.SEND_SMS,
-            Manifest.permission.RECEIVE_SMS,
-            Manifest.permission.READ_SMS,
-            Manifest.permission.RECEIVE_WAP_PUSH,
-            Manifest.permission.RECEIVE_MMS,
-            Manifest.permission.READ_EXTERNAL_STORAGE,
-            Manifest.permission.WRITE_EXTERNAL_STORAGE,
-            Manifest.permission.READ_PHONE_NUMBERS,
-            Manifest.permission.ANSWER_PHONE_CALLS);
-
     /** Permission grant: not grant the permission. */
     private static final int GRANT_DENIED = 1;
     /** Permission grant: grant the permission as an install permission. */
@@ -160,6 +131,7 @@
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final Context mContext;
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     /** Internal storage for permissions and related settings */
     @GuardedBy("mLock")
@@ -1037,10 +1009,10 @@
                 PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
         final PackageParser.Package systemPackage =
                 mPackageManagerInt.getPackage(systemPackageName);
-        boolean allowed = (PackageManagerService.compareSignatures(
+        boolean allowed = (PackageManagerServiceUtils.compareSignatures(
                                 bp.getSourceSignatures(), pkg.mSignatures)
                         == PackageManager.SIGNATURE_MATCH)
-                || (PackageManagerService.compareSignatures(
+                || (PackageManagerServiceUtils.compareSignatures(
                                 systemPackage.mSignatures, pkg.mSignatures)
                         == PackageManager.SIGNATURE_MATCH);
         if (!allowed && (privilegedPermission || oemPermission)) {
@@ -1386,7 +1358,7 @@
         }
 
         if (bp.isRuntime()) {
-            logPermissionGranted(mContext, permName, packageName);
+            logPermission(MetricsEvent.ACTION_PERMISSION_GRANTED, permName, packageName);
         }
 
         if (callback != null) {
@@ -1484,7 +1456,7 @@
         }
 
         if (bp.isRuntime()) {
-            logPermissionRevoked(mContext, permName, packageName);
+            logPermission(MetricsEvent.ACTION_PERMISSION_REVOKED, permName, packageName);
         }
 
         if (callback != null) {
@@ -1938,63 +1910,18 @@
     }
 
     /**
-     * Get the first event id for the permission.
+     * Log that a permission request was granted/revoked.
      *
-     * <p>There are four events for each permission: <ul>
-     *     <li>Request permission: first id + 0</li>
-     *     <li>Grant permission: first id + 1</li>
-     *     <li>Request for permission denied: first id + 2</li>
-     *     <li>Revoke permission: first id + 3</li>
-     * </ul></p>
-     *
+     * @param action the action performed
      * @param name name of the permission
-     *
-     * @return The first event id for the permission
+     * @param packageName package permission is for
      */
-    private static int getBaseEventId(@NonNull String name) {
-        int eventIdIndex = ALL_DANGEROUS_PERMISSIONS.indexOf(name);
+    private void logPermission(int action, @NonNull String name, @NonNull String packageName) {
+        final LogMaker log = new LogMaker(action);
+        log.setPackageName(packageName);
+        log.addTaggedData(MetricsEvent.FIELD_PERMISSION, name);
 
-        if (eventIdIndex == -1) {
-            if (AppOpsManager.permissionToOpCode(name) == AppOpsManager.OP_NONE
-                    || Build.IS_USER) {
-                Log.i(TAG, "Unknown permission " + name);
-
-                return MetricsEvent.ACTION_PERMISSION_REQUEST_UNKNOWN;
-            } else {
-                // Most likely #ALL_DANGEROUS_PERMISSIONS needs to be updated.
-                //
-                // Also update
-                // - EventLogger#ALL_DANGEROUS_PERMISSIONS
-                // - metrics_constants.proto
-                throw new IllegalStateException("Unknown permission " + name);
-            }
-        }
-
-        return MetricsEvent.ACTION_PERMISSION_REQUEST_READ_CALENDAR + eventIdIndex * 4;
-    }
-
-    /**
-     * Log that a permission was revoked.
-     *
-     * @param context Context of the caller
-     * @param name name of the permission
-     * @param packageName package permission if for
-     */
-    private static void logPermissionRevoked(@NonNull Context context, @NonNull String name,
-            @NonNull String packageName) {
-        MetricsLogger.action(context, getBaseEventId(name) + 3, packageName);
-    }
-
-    /**
-     * Log that a permission request was granted.
-     *
-     * @param context Context of the caller
-     * @param name name of the permission
-     * @param packageName package permission if for
-     */
-    private static void logPermissionGranted(@NonNull Context context, @NonNull String name,
-            @NonNull String packageName) {
-        MetricsLogger.action(context, getBaseEventId(name) + 1, packageName);
+        mMetricsLogger.write(log);
     }
 
     private class PermissionManagerInternalImpl extends PermissionManagerInternal {
diff --git a/com/android/server/policy/BurnInProtectionHelper.java b/com/android/server/policy/BurnInProtectionHelper.java
index 92729dc..6886985 100644
--- a/com/android/server/policy/BurnInProtectionHelper.java
+++ b/com/android/server/policy/BurnInProtectionHelper.java
@@ -253,7 +253,8 @@
     public void onDisplayChanged(int displayId) {
         if (displayId == mDisplay.getDisplayId()) {
             if (mDisplay.getState() == Display.STATE_DOZE
-                    || mDisplay.getState() == Display.STATE_DOZE_SUSPEND) {
+                    || mDisplay.getState() == Display.STATE_DOZE_SUSPEND
+                    || mDisplay.getState() == Display.STATE_ON_SUSPEND) {
                 startBurnInProtection();
             } else {
                 cancelBurnInProtection();
diff --git a/com/android/server/policy/GlobalActions.java b/com/android/server/policy/GlobalActions.java
index 7a2e630..3707a5e 100644
--- a/com/android/server/policy/GlobalActions.java
+++ b/com/android/server/policy/GlobalActions.java
@@ -58,7 +58,7 @@
 
     public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
         if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
-        if (mStatusBarInternal.isGlobalActionsDisabled()) {
+        if (mStatusBarInternal != null && mStatusBarInternal.isGlobalActionsDisabled()) {
             return;
         }
         mKeyguardShowing = keyguardShowing;
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index 6520dc9..9162a97 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -59,6 +59,8 @@
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
@@ -66,6 +68,8 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
@@ -122,11 +126,8 @@
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
 
-import static com.android.server.wm.proto.WindowManagerPolicyProto.STABLE_BOUNDS;
-
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerInternal.SleepToken;
 import android.app.ActivityThread;
@@ -183,6 +184,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UEventObserver;
@@ -197,6 +199,7 @@
 import android.service.vr.IPersistentVrStateCallbacks;
 import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
+import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
@@ -207,6 +210,7 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
+import android.view.DisplayFrames;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.IApplicationToken;
@@ -455,6 +459,7 @@
     private AccessibilityShortcutController mAccessibilityShortcutController;
 
     boolean mSafeMode;
+    private final ArraySet<WindowState> mScreenDecorWindows = new ArraySet<>();
     WindowState mStatusBar = null;
     int mStatusBarHeight;
     WindowState mNavigationBar = null;
@@ -592,47 +597,6 @@
 
     PointerLocationView mPointerLocationView;
 
-    // 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.
-    int mOverscanScreenLeft, mOverscanScreenTop;
-    int mOverscanScreenWidth, mOverscanScreenHeight;
-    // 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.
-    int mUnrestrictedScreenLeft, mUnrestrictedScreenTop;
-    int mUnrestrictedScreenWidth, mUnrestrictedScreenHeight;
-    // Like mOverscanScreen*, but allowed to move into the overscan region where appropriate.
-    int mRestrictedOverscanScreenLeft, mRestrictedOverscanScreenTop;
-    int mRestrictedOverscanScreenWidth, mRestrictedOverscanScreenHeight;
-    // 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.
-    int mRestrictedScreenLeft, mRestrictedScreenTop;
-    int mRestrictedScreenWidth, mRestrictedScreenHeight;
-    // During layout, the current screen borders accounting for any currently
-    // visible system UI elements.
-    int mSystemLeft, mSystemTop, mSystemRight, mSystemBottom;
-    // For applications requesting stable content insets, these are them.
-    int mStableLeft, mStableTop, mStableRight, mStableBottom;
-    // For applications requesting stable content insets but have also set the
-    // fullscreen window flag, these are the stable dimensions without the status bar.
-    int mStableFullscreenLeft, mStableFullscreenTop;
-    int mStableFullscreenRight, mStableFullscreenBottom;
-    // During layout, the current screen borders with all outer decoration
-    // (status bar, input method dock) accounted for.
-    int mCurLeft, mCurTop, mCurRight, mCurBottom;
-    // 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 mCur*, but may be larger if the screen decor has supplied
-    // content insets.
-    int mContentLeft, mContentTop, mContentRight, mContentBottom;
-    // 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.
-    int mVoiceContentLeft, mVoiceContentTop, mVoiceContentRight, mVoiceContentBottom;
-    // During layout, the current screen borders along which input method
-    // windows are placed.
-    int mDockLeft, mDockTop, mDockRight, mDockBottom;
     // During layout, the layer at which the doc window is placed.
     int mDockLayer;
     // During layout, this is the layer of the status bar.
@@ -730,18 +694,11 @@
 
     Display mDisplay;
 
-    private int mDisplayRotation;
-
     int mLandscapeRotation = 0;  // default landscape rotation
     int mSeascapeRotation = 0;   // "other" landscape rotation, 180 degrees from mLandscapeRotation
     int mPortraitRotation = 0;   // default portrait rotation
     int mUpsideDownRotation = 0; // "other" portrait rotation
 
-    int mOverscanLeft = 0;
-    int mOverscanTop = 0;
-    int mOverscanRight = 0;
-    int mOverscanBottom = 0;
-
     // What we do when the user long presses on home
     private int mLongPressOnHomeBehavior;
 
@@ -1057,7 +1014,7 @@
             View.NAVIGATION_BAR_UNHIDE,
             View.NAVIGATION_BAR_TRANSLUCENT,
             StatusBarManager.WINDOW_NAVIGATION_BAR,
-            WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
+            FLAG_TRANSLUCENT_NAVIGATION,
             View.NAVIGATION_BAR_TRANSPARENT);
 
     private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
@@ -1168,14 +1125,12 @@
                 + ", mOrientationSensorEnabled=" + mOrientationSensorEnabled
                 + ", mKeyguardDrawComplete=" + mKeyguardDrawComplete
                 + ", mWindowManagerDrawComplete=" + mWindowManagerDrawComplete);
-        final boolean keyguardGoingAway = mWindowManagerInternal.isKeyguardGoingAway();
 
         boolean disable = true;
         // Note: We postpone the rotating of the screen until the keyguard as well as the
         // window manager have reported a draw complete or the keyguard is going away in dismiss
         // mode.
-        if (mScreenOnEarly && mAwake && ((mKeyguardDrawComplete && mWindowManagerDrawComplete)
-                || keyguardGoingAway)) {
+        if (mScreenOnEarly && mAwake && ((mKeyguardDrawComplete && mWindowManagerDrawComplete))) {
             if (needSensorRunningLp()) {
                 disable = false;
                 //enable listener if not already enabled
@@ -1186,7 +1141,7 @@
                     // the sensor reading was cleared which can cause it to relaunch the app that
                     // will show in the wrong orientation first before correcting leading to app
                     // launch delays.
-                    mOrientationListener.enable(!keyguardGoingAway /* clearCurrentRotation */);
+                    mOrientationListener.enable(true /* clearCurrentRotation */);
                     if(localLOGV) Slog.v(TAG, "Enabling listeners");
                     mOrientationSensorEnabled = true;
                 }
@@ -1661,14 +1616,10 @@
 
     private long getAccessibilityShortcutTimeout() {
         ViewConfiguration config = ViewConfiguration.get(mContext);
-        try {
-            return Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, mCurrentUserId) == 0
-                    ? config.getAccessibilityShortcutKeyTimeout()
-                    : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
-        } catch (Settings.SettingNotFoundException e) {
-            throw new RuntimeException(e);
-        }
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, mCurrentUserId) == 0
+                ? config.getAccessibilityShortcutKeyTimeout()
+                : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
     }
 
     private long getScreenshotChordLongPressDelay() {
@@ -2070,6 +2021,7 @@
         context.registerReceiver(mMultiuserReceiver, filter);
 
         // monitor for system gestures
+        // TODO(multi-display): Needs to be display specific.
         mSystemGestures = new SystemGesturesPointerEventListener(context,
                 new SystemGesturesPointerEventListener.Callbacks() {
                     @Override
@@ -2316,17 +2268,6 @@
         return mForceDefaultOrientation;
     }
 
-    @Override
-    public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
-        // TODO(multi-display): Define policy for secondary displays.
-        if (display.getDisplayId() == DEFAULT_DISPLAY) {
-            mOverscanLeft = left;
-            mOverscanTop = top;
-            mOverscanRight = right;
-            mOverscanBottom = bottom;
-        }
-    }
-
     public void updateSettings() {
         ContentResolver resolver = mContext.getContentResolver();
         boolean updateRotation = false;
@@ -2612,7 +2553,19 @@
     }
 
     @Override
-    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
+    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+            boolean hasStatusBarServicePermission) {
+
+        final boolean isScreenDecor = (attrs.privateFlags & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
+        if (mScreenDecorWindows.contains(win)) {
+            if (!isScreenDecor) {
+                // No longer has the flag set, so remove from the set.
+                mScreenDecorWindows.remove(win);
+            }
+        } else if (isScreenDecor && hasStatusBarServicePermission) {
+            mScreenDecorWindows.add(win);
+        }
+
         switch (attrs.type) {
             case TYPE_SYSTEM_OVERLAY:
             case TYPE_SECURE_SYSTEM_OVERLAY:
@@ -3073,6 +3026,14 @@
      */
     @Override
     public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
+
+        if ((attrs.privateFlags & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.STATUS_BAR_SERVICE,
+                    "PhoneWindowManager");
+            mScreenDecorWindows.add(win);
+        }
+
         switch (attrs.type) {
             case TYPE_STATUS_BAR:
                 mContext.enforceCallingOrSelfPermission(
@@ -3124,6 +3085,7 @@
             mNavigationBar = null;
             mNavigationBarController.setWindow(null);
         }
+        mScreenDecorWindows.remove(win);
     }
 
     static final boolean PRINT_ANIM = false;
@@ -4302,12 +4264,16 @@
     }
 
     @Override
+    // TODO: Should probably be moved into DisplayFrames.
     public boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
-            int displayRotation, int displayWidth, int displayHeight, Rect outContentInsets,
-            Rect outStableInsets, Rect outOutsets) {
+            DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets,
+            Rect outOutsets) {
         final int fl = PolicyControl.getWindowFlags(null, attrs);
         final int sysuiVis = PolicyControl.getSystemUiVisibility(null, attrs);
         final int systemUiVisibility = (sysuiVis | attrs.subtreeSystemUiVisibility);
+        final int displayRotation = displayFrames.mRotation;
+        final int displayWidth = displayFrames.mDisplayWidth;
+        final int displayHeight = displayFrames.mDisplayHeight;
 
         final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl);
         if (useOutsets) {
@@ -4330,34 +4296,33 @@
             int availRight, availBottom;
             if (canHideNavigationBar() &&
                     (systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
-                availRight = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                availBottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                availRight = displayFrames.mUnrestricted.right;
+                availBottom = displayFrames.mUnrestricted.bottom;
             } else {
-                availRight = mRestrictedScreenLeft + mRestrictedScreenWidth;
-                availBottom = mRestrictedScreenTop + mRestrictedScreenHeight;
+                availRight = displayFrames.mRestricted.right;
+                availBottom = displayFrames.mRestricted.bottom;
             }
+            outStableInsets.set(displayFrames.mStable.left, displayFrames.mStable.top,
+                    availRight - displayFrames.mStable.right,
+                    availBottom - displayFrames.mStable.bottom);
+
             if ((systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
                 if ((fl & FLAG_FULLSCREEN) != 0) {
-                    outContentInsets.set(mStableFullscreenLeft, mStableFullscreenTop,
-                            availRight - mStableFullscreenRight,
-                            availBottom - mStableFullscreenBottom);
+                    outContentInsets.set(displayFrames.mStableFullscreen.left,
+                            displayFrames.mStableFullscreen.top,
+                            availRight - displayFrames.mStableFullscreen.right,
+                            availBottom - displayFrames.mStableFullscreen.bottom);
                 } else {
-                    outContentInsets.set(mStableLeft, mStableTop,
-                            availRight - mStableRight, availBottom - mStableBottom);
+                    outContentInsets.set(outStableInsets);
                 }
             } else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {
                 outContentInsets.setEmpty();
-            } else if ((systemUiVisibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
-                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) == 0) {
-                outContentInsets.set(mCurLeft, mCurTop,
-                        availRight - mCurRight, availBottom - mCurBottom);
             } else {
-                outContentInsets.set(mCurLeft, mCurTop,
-                        availRight - mCurRight, availBottom - mCurBottom);
+                outContentInsets.set(displayFrames.mCurrent.left, displayFrames.mCurrent.top,
+                        availRight - displayFrames.mCurrent.right,
+                        availBottom - displayFrames.mCurrent.bottom);
             }
 
-            outStableInsets.set(mStableLeft, mStableTop,
-                    availRight - mStableRight, availBottom - mStableBottom);
             if (taskBounds != null) {
                 calculateRelevantTaskInsets(taskBounds, outContentInsets,
                         displayWidth, displayHeight);
@@ -4394,67 +4359,11 @@
 
     /** {@inheritDoc} */
     @Override
-    public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
-                              int displayRotation, int uiMode) {
-        mDisplayRotation = displayRotation;
-        final int overscanLeft, overscanTop, overscanRight, overscanBottom;
-        if (isDefaultDisplay) {
-            switch (displayRotation) {
-                case Surface.ROTATION_90:
-                    overscanLeft = mOverscanTop;
-                    overscanTop = mOverscanRight;
-                    overscanRight = mOverscanBottom;
-                    overscanBottom = mOverscanLeft;
-                    break;
-                case Surface.ROTATION_180:
-                    overscanLeft = mOverscanRight;
-                    overscanTop = mOverscanBottom;
-                    overscanRight = mOverscanLeft;
-                    overscanBottom = mOverscanTop;
-                    break;
-                case Surface.ROTATION_270:
-                    overscanLeft = mOverscanBottom;
-                    overscanTop = mOverscanLeft;
-                    overscanRight = mOverscanTop;
-                    overscanBottom = mOverscanRight;
-                    break;
-                default:
-                    overscanLeft = mOverscanLeft;
-                    overscanTop = mOverscanTop;
-                    overscanRight = mOverscanRight;
-                    overscanBottom = mOverscanBottom;
-                    break;
-            }
-        } else {
-            overscanLeft = 0;
-            overscanTop = 0;
-            overscanRight = 0;
-            overscanBottom = 0;
-        }
-        mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
-        mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
-        mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
-        mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
-        mSystemLeft = 0;
-        mSystemTop = 0;
-        mSystemRight = displayWidth;
-        mSystemBottom = displayHeight;
-        mUnrestrictedScreenLeft = overscanLeft;
-        mUnrestrictedScreenTop = overscanTop;
-        mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;
-        mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
-        mRestrictedScreenLeft = mUnrestrictedScreenLeft;
-        mRestrictedScreenTop = mUnrestrictedScreenTop;
-        mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
-        mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
-        mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft
-                = mCurLeft = mUnrestrictedScreenLeft;
-        mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop
-                = mCurTop = mUnrestrictedScreenTop;
-        mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight
-                = mCurRight = displayWidth - overscanRight;
-        mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom
-                = mCurBottom = displayHeight - overscanBottom;
+    public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
+        displayFrames.onBeginLayout();
+        // TODO(multi-display): This doesn't seem right...Maybe only apply to default display?
+        mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
+        mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
         mDockLayer = 0x10000000;
         mStatusBarLayer = -1;
 
@@ -4464,13 +4373,13 @@
         final Rect of = mTmpOverscanFrame;
         final Rect vf = mTmpVisibleFrame;
         final Rect dcf = mTmpDecorFrame;
-        pf.left = df.left = of.left = vf.left = mDockLeft;
-        pf.top = df.top = of.top = vf.top = mDockTop;
-        pf.right = df.right = of.right = vf.right = mDockRight;
-        pf.bottom = df.bottom = of.bottom = vf.bottom = mDockBottom;
+        vf.set(displayFrames.mDock);
+        of.set(displayFrames.mDock);
+        df.set(displayFrames.mDock);
+        pf.set(displayFrames.mDock);
         dcf.setEmpty();  // Decor frame N/A for system bars.
 
-        if (isDefaultDisplay) {
+        if (displayFrames.mDisplayId == DEFAULT_DISPLAY) {
             // For purposes of putting out fake window up to steal focus, we will
             // drive nav being hidden only by whether it is requested.
             final int sysui = mLastSystemUiFlags;
@@ -4489,10 +4398,9 @@
                     && mStatusBar.getAttrs().height == MATCH_PARENT
                     && mStatusBar.getAttrs().width == MATCH_PARENT;
 
-            // When the navigation bar isn't visible, we put up a fake
-            // input window to catch all touch events.  This way we can
-            // detect when the user presses anywhere to bring back the nav
-            // bar and ensure the application doesn't see the event.
+            // When the navigation bar isn't visible, we put up a fake input window to catch all
+            // touch events. This way we can detect when the user presses anywhere to bring back the
+            // nav bar and ensure the application doesn't see the event.
             if (navVisible || navAllowedHidden) {
                 if (mInputConsumer != null) {
                     mHandler.sendMessage(
@@ -4508,199 +4416,237 @@
                 InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL);
             }
 
-            // For purposes of positioning and showing the nav bar, if we have
-            // decided that it can't be hidden (because of the screen aspect ratio),
-            // then take that into account.
+            // For purposes of positioning and showing the nav bar, if we have decided that it can't
+            // be hidden (because of the screen aspect ratio), then take that into account.
             navVisible |= !canHideNavigationBar();
 
-            boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,
-                    displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,
-                    navAllowedHidden, statusBarExpandedNotKeyguard);
-            if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
-                    mDockLeft, mDockTop, mDockRight, mDockBottom));
-            updateSysUiVisibility |= layoutStatusBar(pf, df, of, vf, dcf, sysui, isKeyguardShowing);
+            boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, dcf,
+                    navVisible, navTranslucent, navAllowedHidden, statusBarExpandedNotKeyguard);
+            if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock);
+            updateSysUiVisibility |= layoutStatusBar(
+                    displayFrames, pf, df, of, vf, dcf, sysui, isKeyguardShowing);
             if (updateSysUiVisibility) {
                 updateSystemUiVisibilityLw();
             }
         }
+        layoutScreenDecorWindows(displayFrames, pf, df, dcf);
     }
 
-    private boolean layoutStatusBar(Rect pf, Rect df, Rect of, Rect vf, Rect dcf, int sysui,
-            boolean isKeyguardShowing) {
-        // decide where the status bar goes ahead of time
-        if (mStatusBar != null) {
-            // apply any navigation bar insets
-            pf.left = df.left = of.left = mUnrestrictedScreenLeft;
-            pf.top = df.top = of.top = mUnrestrictedScreenTop;
-            pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
-            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight
-                    + mUnrestrictedScreenTop;
-            vf.left = mStableLeft;
-            vf.top = mStableTop;
-            vf.right = mStableRight;
-            vf.bottom = mStableBottom;
+    private void layoutScreenDecorWindows(DisplayFrames displayFrames, Rect pf, Rect df, Rect dcf) {
+        if (mScreenDecorWindows.isEmpty()) {
+            return;
+        }
 
-            mStatusBarLayer = mStatusBar.getSurfaceLayer();
+        final int displayId = displayFrames.mDisplayId;
+        final Rect dockFrame = displayFrames.mDock;
+        final int displayHeight = displayFrames.mDisplayHeight;
+        final int displayWidth = displayFrames.mDisplayWidth;
 
-            // Let the status bar determine its size.
-            mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
-                    vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
-                    dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */);
-
-            // For layout, the status bar is always at the top with our fixed height.
-            mStableTop = mUnrestrictedScreenTop + mStatusBarHeight;
-
-            boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
-            boolean statusBarTranslucent = (sysui
-                    & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
-            if (!isKeyguardShowing) {
-                statusBarTranslucent &= areTranslucentBarsAllowed();
+        for (int i = mScreenDecorWindows.size() - 1; i >= 0; --i) {
+            final WindowState w = mScreenDecorWindows.valueAt(i);
+            if (w.getDisplayId() != displayId || !w.isVisibleLw()) {
+                // Skip if not on the same display or not visible.
+                continue;
             }
 
-            // If the status bar is hidden, we don't want to cause
-            // windows behind it to scroll.
-            if (mStatusBar.isVisibleLw() && !statusBarTransient) {
-                // Status bar may go away, so the screen area it occupies
-                // is available to apps but just covering them when the
-                // status bar is visible.
-                mDockTop = mUnrestrictedScreenTop + mStatusBarHeight;
+            w.computeFrameLw(pf /* parentFrame */, df /* displayFrame */, df /* overlayFrame */,
+                    df /* contentFrame */, df /* visibleFrame */, dcf /* decorFrame */,
+                    df /* stableFrame */, df /* outsetFrame */);
+            final Rect frame = w.getFrameLw();
 
-                mContentTop = mVoiceContentTop = mCurTop = mDockTop;
-                mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
-                mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
-                mContentRight = mVoiceContentRight = mCurRight = mDockRight;
-
-                if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " +
-                        String.format(
-                                "dock=[%d,%d][%d,%d] content=[%d,%d][%d,%d] cur=[%d,%d][%d,%d]",
-                                mDockLeft, mDockTop, mDockRight, mDockBottom,
-                                mContentLeft, mContentTop, mContentRight, mContentBottom,
-                                mCurLeft, mCurTop, mCurRight, mCurBottom));
-            }
-            if (mStatusBar.isVisibleLw() && !mStatusBar.isAnimatingLw()
-                    && !statusBarTransient && !statusBarTranslucent
-                    && !mStatusBarController.wasRecentlyTranslucent()) {
-                // If the opaque status bar is currently requested to be visible,
-                // and not in the process of animating on or off, then
-                // we can tell the app that it is covered by it.
-                mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight;
-            }
-            if (mStatusBarController.checkHiddenLw()) {
-                return true;
+            if (frame.left <= 0 && frame.top <= 0) {
+                // Docked at left or top.
+                if (frame.bottom >= displayHeight) {
+                    // Docked left.
+                    dockFrame.left = Math.max(frame.right, dockFrame.left);
+                } else if (frame.right >= displayWidth ) {
+                    // Docked top.
+                    dockFrame.top = Math.max(frame.bottom, dockFrame.top);
+                } else {
+                    Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+                            + " not docked on left or top of display. frame=" + frame
+                            + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+                }
+            } else if (frame.right >= displayWidth && frame.bottom >= displayHeight) {
+                // Docked at right or bottom.
+                if (frame.top <= 0) {
+                    // Docked right.
+                    dockFrame.right = Math.min(frame.left, dockFrame.right);
+                } else if (frame.left <= 0) {
+                    // Docked bottom.
+                    dockFrame.bottom = Math.min(frame.top, dockFrame.bottom);
+                } else {
+                    Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+                            + " not docked on right or bottom" + " of display. frame=" + frame
+                            + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+                }
+            } else {
+                // Screen decor windows are required to be docked on one of the sides of the screen.
+                Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+                        + " not docked on one of the sides of the display. frame=" + frame
+                        + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
             }
         }
-        return false;
+
+        displayFrames.mRestricted.set(dockFrame);
+        displayFrames.mCurrent.set(dockFrame);
+        displayFrames.mVoiceContent.set(dockFrame);
+        displayFrames.mSystem.set(dockFrame);
+        displayFrames.mContent.set(dockFrame);
+        displayFrames.mRestrictedOverscan.set(dockFrame);
     }
 
-    private boolean layoutNavigationBar(int displayWidth, int displayHeight, int displayRotation,
-            int uiMode, int overscanLeft, int overscanRight, int overscanBottom, Rect dcf,
+    private boolean layoutStatusBar(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect vf,
+            Rect dcf, int sysui, boolean isKeyguardShowing) {
+        // decide where the status bar goes ahead of time
+        if (mStatusBar == null) {
+            return false;
+        }
+        // apply any navigation bar insets
+        of.set(displayFrames.mUnrestricted);
+        df.set(displayFrames.mUnrestricted);
+        pf.set(displayFrames.mUnrestricted);
+        vf.set(displayFrames.mStable);
+
+        mStatusBarLayer = mStatusBar.getSurfaceLayer();
+
+        // Let the status bar determine its size.
+        mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
+                vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
+                dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */);
+
+        // For layout, the status bar is always at the top with our fixed height.
+        displayFrames.mStable.top = displayFrames.mUnrestricted.top + mStatusBarHeight;
+
+        boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
+        boolean statusBarTranslucent = (sysui
+                & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
+        if (!isKeyguardShowing) {
+            statusBarTranslucent &= areTranslucentBarsAllowed();
+        }
+
+        // If the status bar is hidden, we don't want to cause windows behind it to scroll.
+        if (mStatusBar.isVisibleLw() && !statusBarTransient) {
+            // Status bar may go away, so the screen area it occupies is available to apps but just
+            // covering them when the status bar is visible.
+            final Rect dockFrame = displayFrames.mDock;
+            dockFrame.top = displayFrames.mStable.top;
+            displayFrames.mContent.set(dockFrame);
+            displayFrames.mVoiceContent.set(dockFrame);
+
+            if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + String.format(
+                    "dock=%s content=%s cur=%s", dockFrame.toString(),
+                    displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));
+
+            if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
+                    && !mStatusBarController.wasRecentlyTranslucent()) {
+                // If the opaque status bar is currently requested to be visible, and not in the
+                // process of animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.top = displayFrames.mStable.top;
+            }
+        }
+        return mStatusBarController.checkHiddenLw();
+    }
+
+    private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, Rect dcf,
             boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
             boolean statusBarExpandedNotKeyguard) {
-        if (mNavigationBar != null) {
-            boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
-            // Force the navigation bar to its appropriate place and
-            // size.  We need to do this directly, instead of relying on
-            // it to bubble up from the nav bar, because this needs to
-            // change atomically with screen rotations.
-            mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight,
-                    displayRotation);
-            if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
-                // It's a system nav bar or a portrait screen; nav bar goes on bottom.
-                int top = displayHeight - overscanBottom
-                        - getNavigationBarHeight(displayRotation, uiMode);
-                mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom);
-                mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top;
-                if (transientNavBarShowing) {
-                    mNavigationBarController.setBarShowingLw(true);
-                } else if (navVisible) {
-                    mNavigationBarController.setBarShowingLw(true);
-                    mDockBottom = mTmpNavigationFrame.top;
-                    mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
-                    mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop;
-                } else {
-                    // We currently want to hide the navigation UI - unless we expanded the status
-                    // bar.
-                    mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
-                }
-                if (navVisible && !navTranslucent && !navAllowedHidden
-                        && !mNavigationBar.isAnimatingLw()
-                        && !mNavigationBarController.wasRecentlyTranslucent()) {
-                    // If the opaque nav bar is currently requested to be visible,
-                    // and not in the process of animating on or off, then
-                    // we can tell the app that it is covered by it.
-                    mSystemBottom = mTmpNavigationFrame.top;
-                }
-            } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
-                // Landscape screen; nav bar goes to the right.
-                int left = displayWidth - overscanRight
-                        - getNavigationBarWidth(displayRotation, uiMode);
-                mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight);
-                mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left;
-                if (transientNavBarShowing) {
-                    mNavigationBarController.setBarShowingLw(true);
-                } else if (navVisible) {
-                    mNavigationBarController.setBarShowingLw(true);
-                    mDockRight = mTmpNavigationFrame.left;
-                    mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
-                    mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
-                } else {
-                    // We currently want to hide the navigation UI - unless we expanded the status
-                    // bar.
-                    mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
-                }
-                if (navVisible && !navTranslucent && !navAllowedHidden
-                        && !mNavigationBar.isAnimatingLw()
-                        && !mNavigationBarController.wasRecentlyTranslucent()) {
-                    // If the nav bar is currently requested to be visible,
-                    // and not in the process of animating on or off, then
-                    // we can tell the app that it is covered by it.
-                    mSystemRight = mTmpNavigationFrame.left;
-                }
-            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
-                // Seascape screen; nav bar goes to the left.
-                int right = overscanLeft + getNavigationBarWidth(displayRotation, uiMode);
-                mTmpNavigationFrame.set(overscanLeft, 0, right, displayHeight);
-                mStableLeft = mStableFullscreenLeft = mTmpNavigationFrame.right;
-                if (transientNavBarShowing) {
-                    mNavigationBarController.setBarShowingLw(true);
-                } else if (navVisible) {
-                    mNavigationBarController.setBarShowingLw(true);
-                    mDockLeft = mTmpNavigationFrame.right;
-                    // TODO: not so sure about those:
-                    mRestrictedScreenLeft = mRestrictedOverscanScreenLeft = mDockLeft;
-                    mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
-                    mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
-                } else {
-                    // We currently want to hide the navigation UI - unless we expanded the status
-                    // bar.
-                    mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
-                }
-                if (navVisible && !navTranslucent && !navAllowedHidden
-                        && !mNavigationBar.isAnimatingLw()
-                        && !mNavigationBarController.wasRecentlyTranslucent()) {
-                    // If the nav bar is currently requested to be visible,
-                    // and not in the process of animating on or off, then
-                    // we can tell the app that it is covered by it.
-                    mSystemLeft = mTmpNavigationFrame.right;
-                }
+        if (mNavigationBar == null) {
+            return false;
+        }
+        boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
+        // Force the navigation bar to its appropriate place and size. We need to do this directly,
+        // instead of relying on it to bubble up from the nav bar, because this needs to change
+        // atomically with screen rotations.
+        final int rotation = displayFrames.mRotation;
+        final int displayHeight = displayFrames.mDisplayHeight;
+        final int displayWidth = displayFrames.mDisplayWidth;
+        final Rect dockFrame = displayFrames.mDock;
+        mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);
+
+        if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
+            // It's a system nav bar or a portrait screen; nav bar goes on bottom.
+            final int top = displayFrames.mUnrestricted.bottom
+                    - getNavigationBarHeight(rotation, uiMode);
+            mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom);
+            displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
+            if (transientNavBarShowing) {
+                mNavigationBarController.setBarShowingLw(true);
+            } else if (navVisible) {
+                mNavigationBarController.setBarShowingLw(true);
+                dockFrame.bottom = displayFrames.mRestricted.bottom
+                        = displayFrames.mRestrictedOverscan.bottom = top;
+            } else {
+                // We currently want to hide the navigation UI - unless we expanded the status bar.
+                mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
             }
-            // Make sure the content and current rectangles are updated to
-            // account for the restrictions from the navigation bar.
-            mContentTop = mVoiceContentTop = mCurTop = mDockTop;
-            mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
-            mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
-            mContentRight = mVoiceContentRight = mCurRight = mDockRight;
-            mStatusBarLayer = mNavigationBar.getSurfaceLayer();
-            // And compute the final frame.
-            mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
-                    mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
-                    mTmpNavigationFrame, mTmpNavigationFrame);
-            if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
-            if (mNavigationBarController.checkHiddenLw()) {
-                return true;
+            if (navVisible && !navTranslucent && !navAllowedHidden
+                    && !mNavigationBar.isAnimatingLw()
+                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                // If the opaque nav bar is currently requested to be visible and not in the process
+                // of animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.bottom = top;
+            }
+        } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
+            // Landscape screen; nav bar goes to the right.
+            final int left = displayFrames.mUnrestricted.right
+                    - getNavigationBarWidth(rotation, uiMode);
+            mTmpNavigationFrame.set(left, 0, displayFrames.mUnrestricted.right, displayHeight);
+            displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left;
+            if (transientNavBarShowing) {
+                mNavigationBarController.setBarShowingLw(true);
+            } else if (navVisible) {
+                mNavigationBarController.setBarShowingLw(true);
+                dockFrame.right = displayFrames.mRestricted.right
+                        = displayFrames.mRestrictedOverscan.right = left;
+            } else {
+                // We currently want to hide the navigation UI - unless we expanded the status bar.
+                mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
+            }
+            if (navVisible && !navTranslucent && !navAllowedHidden
+                    && !mNavigationBar.isAnimatingLw()
+                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                // If the nav bar is currently requested to be visible, and not in the process of
+                // animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.right = left;
+            }
+        } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+            // Seascape screen; nav bar goes to the left.
+            final int right = displayFrames.mUnrestricted.left
+                    + getNavigationBarWidth(rotation, uiMode);
+            mTmpNavigationFrame.set(displayFrames.mUnrestricted.left, 0, right, displayHeight);
+            displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right;
+            if (transientNavBarShowing) {
+                mNavigationBarController.setBarShowingLw(true);
+            } else if (navVisible) {
+                mNavigationBarController.setBarShowingLw(true);
+                dockFrame.left = displayFrames.mRestricted.left =
+                        displayFrames.mRestrictedOverscan.left = right;
+            } else {
+                // We currently want to hide the navigation UI - unless we expanded the status bar.
+                mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
+            }
+            if (navVisible && !navTranslucent && !navAllowedHidden
+                    && !mNavigationBar.isAnimatingLw()
+                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                // If the nav bar is currently requested to be visible, and not in the process of
+                // animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.left = right;
             }
         }
-        return false;
+
+        // Make sure the content and current rectangles are updated to account for the restrictions
+        // from the navigation bar.
+        displayFrames.mCurrent.set(dockFrame);
+        displayFrames.mVoiceContent.set(dockFrame);
+        displayFrames.mContent.set(dockFrame);
+        mStatusBarLayer = mNavigationBar.getSurfaceLayer();
+        // And compute the final frame.
+        mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
+                mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
+                mTmpNavigationFrame, mTmpNavigationFrame);
+        if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
+        return mNavigationBarController.checkHiddenLw();
     }
 
     private int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
@@ -4728,32 +4674,26 @@
         return 0;
     }
 
-    @Override
-    public void getContentRectLw(Rect r) {
-        r.set(mContentLeft, mContentTop, mContentRight, mContentBottom);
-    }
-
-    void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
-            boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf) {
+    private void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
+            boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf,
+            DisplayFrames displayFrames) {
         if (win.getSurfaceLayer() > mDockLayer && attached.getSurfaceLayer() < mDockLayer) {
-            // Here's a special case: if this attached window is a panel that is
-            // above the dock window, and the window it is attached to is below
-            // the dock window, then the frames we computed for the window it is
-            // attached to can not be used because the dock is effectively part
-            // of the underlying window and the attached window is floating on top
-            // of the whole thing.  So, we ignore the attached window and explicitly
-            // compute the frames that would be appropriate without the dock.
-            df.left = of.left = cf.left = vf.left = mDockLeft;
-            df.top = of.top = cf.top = vf.top = mDockTop;
-            df.right = of.right = cf.right = vf.right = mDockRight;
-            df.bottom = of.bottom = cf.bottom = vf.bottom = mDockBottom;
+            // Here's a special case: if this attached window is a panel that is above the dock
+            // window, and the window it is attached to is below the dock window, then the frames we
+            // computed for the window it is attached to can not be used because the dock is
+            // effectively part of the underlying window and the attached window is floating on top
+            // of the whole thing. So, we ignore the attached window and explicitly compute the
+            // frames that would be appropriate without the dock.
+            vf.set(displayFrames.mDock);
+            cf.set(displayFrames.mDock);
+            of.set(displayFrames.mDock);
+            df.set(displayFrames.mDock);
         } else {
-            // The effective display frame of the attached window depends on
-            // whether it is taking care of insetting its content.  If not,
-            // we need to use the parent's content frame so that the entire
-            // window is positioned within that content.  Otherwise we can use
-            // the overscan frame and let the attached window take care of
-            // positioning its content appropriately.
+            // The effective display frame of the attached window depends on whether it is taking
+            // care of insetting its content. If not, we need to use the parent's content frame so
+            // that the entire window is positioned within that content. Otherwise we can use the
+            // overscan frame and let the attached window take care of positioning its content
+            // appropriately.
             if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
                 // Set the content frame of the attached window to the parent's decor frame
                 // (same as content frame when IME isn't present) if specifically requested by
@@ -4762,51 +4702,37 @@
                 cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0
                         ? attached.getContentFrameLw() : attached.getOverscanFrameLw());
             } else {
-                // If the window is resizing, then we want to base the content
-                // frame on our attached content frame to resize...  however,
-                // things can be tricky if the attached window is NOT in resize
-                // mode, in which case its content frame will be larger.
-                // Ungh.  So to deal with that, make sure the content frame
-                // we end up using is not covering the IM dock.
+                // If the window is resizing, then we want to base the content frame on our attached
+                // content frame to resize...however, things can be tricky if the attached window is
+                // NOT in resize mode, in which case its content frame will be larger.
+                // Ungh. So to deal with that, make sure the content frame we end up using is not
+                // covering the IM dock.
                 cf.set(attached.getContentFrameLw());
                 if (attached.isVoiceInteraction()) {
-                    if (cf.left < mVoiceContentLeft) cf.left = mVoiceContentLeft;
-                    if (cf.top < mVoiceContentTop) cf.top = mVoiceContentTop;
-                    if (cf.right > mVoiceContentRight) cf.right = mVoiceContentRight;
-                    if (cf.bottom > mVoiceContentBottom) cf.bottom = mVoiceContentBottom;
+                    cf.intersectUnchecked(displayFrames.mVoiceContent);
                 } else if (attached.getSurfaceLayer() < mDockLayer) {
-                    if (cf.left < mContentLeft) cf.left = mContentLeft;
-                    if (cf.top < mContentTop) cf.top = mContentTop;
-                    if (cf.right > mContentRight) cf.right = mContentRight;
-                    if (cf.bottom > mContentBottom) cf.bottom = mContentBottom;
+                    cf.intersectUnchecked(displayFrames.mContent);
                 }
             }
             df.set(insetDecors ? attached.getDisplayFrameLw() : cf);
             of.set(insetDecors ? attached.getOverscanFrameLw() : cf);
             vf.set(attached.getVisibleFrameLw());
         }
-        // The LAYOUT_IN_SCREEN flag is used to determine whether the attached
-        // window should be positioned relative to its parent or the entire
-        // screen.
-        pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0
-                ? attached.getFrameLw() : df);
+        // The LAYOUT_IN_SCREEN flag is used to determine whether the attached window should be
+        // positioned relative to its parent or the entire screen.
+        pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df);
     }
 
-    private void applyStableConstraints(int sysui, int fl, Rect r) {
-        if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
-            // If app is requesting a stable layout, don't let the
-            // content insets go below the stable values.
-            if ((fl & FLAG_FULLSCREEN) != 0) {
-                if (r.left < mStableFullscreenLeft) r.left = mStableFullscreenLeft;
-                if (r.top < mStableFullscreenTop) r.top = mStableFullscreenTop;
-                if (r.right > mStableFullscreenRight) r.right = mStableFullscreenRight;
-                if (r.bottom > mStableFullscreenBottom) r.bottom = mStableFullscreenBottom;
-            } else {
-                if (r.left < mStableLeft) r.left = mStableLeft;
-                if (r.top < mStableTop) r.top = mStableTop;
-                if (r.right > mStableRight) r.right = mStableRight;
-                if (r.bottom > mStableBottom) r.bottom = mStableBottom;
-            }
+    private void applyStableConstraints(int sysui, int fl, Rect r, DisplayFrames displayFrames) {
+        if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) == 0) {
+            return;
+        }
+        // If app is requesting a stable layout, don't let the content insets go below the stable
+        // values.
+        if ((fl & FLAG_FULLSCREEN) != 0) {
+            r.intersectUnchecked(displayFrames.mStableFullscreen);
+        } else {
+            r.intersectUnchecked(displayFrames.mStable);
         }
     }
 
@@ -4821,10 +4747,12 @@
 
     /** {@inheritDoc} */
     @Override
-    public void layoutWindowLw(WindowState win, WindowState attached) {
-        // We've already done the navigation bar and status bar. If the status bar can receive
-        // input, we need to layout it again to accomodate for the IME window.
-        if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {
+    public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
+        // We've already done the navigation bar, status bar, and all screen decor windows. If the
+        // status bar can receive input, we need to layout it again to accommodate for the IME
+        // window.
+        if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar
+                || mScreenDecorWindows.contains(win)) {
             return;
         }
         final WindowManager.LayoutParams attrs = win.getAttrs();
@@ -4833,9 +4761,10 @@
                 (win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
         if (needsToOffsetInputMethodTarget) {
             if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state");
-            offsetInputMethodWindowLw(mLastInputMethodWindow);
+            offsetInputMethodWindowLw(mLastInputMethodWindow, displayFrames);
         }
 
+        final int type = attrs.type;
         final int fl = PolicyControl.getWindowFlags(win, attrs);
         final int pfl = attrs.privateFlags;
         final int sim = attrs.softInputMode;
@@ -4856,119 +4785,83 @@
 
         final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
 
-        if (isDefaultDisplay) {
-            sf.set(mStableLeft, mStableTop, mStableRight, mStableBottom);
-        } else {
-            sf.set(mOverscanLeft, mOverscanTop, mOverscanRight, mOverscanBottom);
-        }
+        sf.set(displayFrames.mStable);
 
-        if (!isDefaultDisplay) {
-            if (attached != null) {
-                // If this window is attached to another, our display
-                // frame is the same as the one we are attached to.
-                setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
-            } else {
-                // Give the window full screen.
-                pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
-                pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
-                pf.right = df.right = of.right = cf.right
-                        = mOverscanScreenLeft + mOverscanScreenWidth;
-                pf.bottom = df.bottom = of.bottom = cf.bottom
-                        = mOverscanScreenTop + mOverscanScreenHeight;
-            }
-        } else if (attrs.type == TYPE_INPUT_METHOD) {
-            pf.left = df.left = of.left = cf.left = vf.left = mDockLeft;
-            pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
-            pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
+        if (type == TYPE_INPUT_METHOD) {
+            vf.set(displayFrames.mDock);
+            cf.set(displayFrames.mDock);
+            of.set(displayFrames.mDock);
+            df.set(displayFrames.mDock);
+            pf.set(displayFrames.mDock);
             // IM dock windows layout below the nav bar...
-            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+            pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;
             // ...with content insets above the nav bar
-            cf.bottom = vf.bottom = mStableBottom;
+            cf.bottom = vf.bottom = displayFrames.mStable.bottom;
             if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
                 // The status bar forces the navigation bar while it's visible. Make sure the IME
                 // avoids the navigation bar in that case.
                 if (mNavigationBarPosition == NAV_BAR_RIGHT) {
-                    pf.right = df.right = of.right = cf.right = vf.right = mStableRight;
+                    pf.right = df.right = of.right = cf.right = vf.right =
+                            displayFrames.mStable.right;
                 } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
-                    pf.left = df.left = of.left = cf.left = vf.left = mStableLeft;
+                    pf.left = df.left = of.left = cf.left = vf.left = displayFrames.mStable.left;
                 }
             }
             // IM dock windows always go to the bottom of the screen.
             attrs.gravity = Gravity.BOTTOM;
             mDockLayer = win.getSurfaceLayer();
-        } else if (attrs.type == TYPE_VOICE_INTERACTION) {
-            pf.left = df.left = of.left = mUnrestrictedScreenLeft;
-            pf.top = df.top = of.top = mUnrestrictedScreenTop;
-            pf.right = df.right = of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+        } else if (type == TYPE_VOICE_INTERACTION) {
+            of.set(displayFrames.mUnrestricted);
+            df.set(displayFrames.mUnrestricted);
+            pf.set(displayFrames.mUnrestricted);
             if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
-                cf.left = mDockLeft;
-                cf.top = mDockTop;
-                cf.right = mDockRight;
-                cf.bottom = mDockBottom;
+                cf.set(displayFrames.mDock);
             } else {
-                cf.left = mContentLeft;
-                cf.top = mContentTop;
-                cf.right = mContentRight;
-                cf.bottom = mContentBottom;
+                cf.set(displayFrames.mContent);
             }
             if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
-                vf.left = mCurLeft;
-                vf.top = mCurTop;
-                vf.right = mCurRight;
-                vf.bottom = mCurBottom;
+                vf.set(displayFrames.mCurrent);
             } else {
                 vf.set(cf);
             }
-        } else if (attrs.type == TYPE_WALLPAPER) {
-           layoutWallpaper(win, pf, df, of, cf);
+        } else if (type == TYPE_WALLPAPER) {
+           layoutWallpaper(displayFrames, pf, df, of, cf);
         } else if (win == mStatusBar) {
-            pf.left = df.left = of.left = mUnrestrictedScreenLeft;
-            pf.top = df.top = of.top = mUnrestrictedScreenTop;
-            pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
-            pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight + mUnrestrictedScreenTop;
-            cf.left = vf.left = mStableLeft;
-            cf.top = vf.top = mStableTop;
-            cf.right = vf.right = mStableRight;
-            vf.bottom = mStableBottom;
+            of.set(displayFrames.mUnrestricted);
+            df.set(displayFrames.mUnrestricted);
+            pf.set(displayFrames.mUnrestricted);
+            cf.set(displayFrames.mStable);
+            vf.set(displayFrames.mStable);
 
             if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
-                cf.bottom = mContentBottom;
+                cf.bottom = displayFrames.mContent.bottom;
             } else {
-                cf.bottom = mDockBottom;
-                vf.bottom = mContentBottom;
+                cf.bottom = displayFrames.mDock.bottom;
+                vf.bottom = displayFrames.mContent.bottom;
             }
         } else {
-
-            // Default policy decor for the default display
-            dcf.left = mSystemLeft;
-            dcf.top = mSystemTop;
-            dcf.right = mSystemRight;
-            dcf.bottom = mSystemBottom;
-            final boolean inheritTranslucentDecor = (attrs.privateFlags
-                    & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
+            dcf.set(displayFrames.mSystem);
+            final boolean inheritTranslucentDecor =
+                    (attrs.privateFlags & PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
             final boolean isAppWindow =
-                    attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW &&
-                    attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+                    type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW;
             final boolean topAtRest =
                     win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
             if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
                 if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
-                        && (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0
-                        && (fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) == 0
-                        && (fl & WindowManager.LayoutParams.
-                                FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
+                        && (fl & FLAG_FULLSCREEN) == 0
+                        && (fl & FLAG_TRANSLUCENT_STATUS) == 0
+                        && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
                         && (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {
                     // Ensure policy decor includes status bar
-                    dcf.top = mStableTop;
+                    dcf.top = displayFrames.mStable.top;
                 }
-                if ((fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == 0
+                if ((fl & FLAG_TRANSLUCENT_NAVIGATION) == 0
                         && (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
-                        && (fl & WindowManager.LayoutParams.
-                                FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
+                        && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
                     // Ensure policy decor includes navigation bar
-                    dcf.bottom = mStableBottom;
-                    dcf.right = mStableRight;
+                    dcf.bottom = displayFrames.mStable.bottom;
+                    dcf.right = displayFrames.mStable.right;
                 }
             }
 
@@ -4976,117 +4869,83 @@
                     == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
                             + "): IN_SCREEN, INSET_DECOR");
-                // This is the case for a normal activity window: we want it
-                // to cover all of the screen space, and it can take care of
-                // moving its contents to account for screen decorations that
-                // intrude into that space.
+                // This is the case for a normal activity window: we want it to cover all of the
+                // screen space, and it can take care of moving its contents to account for screen
+                // decorations that intrude into that space.
                 if (attached != null) {
                     // If this window is attached to another, our display
                     // frame is the same as the one we are attached to.
-                    setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
+                    setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf,
+                            displayFrames);
                 } else {
-                    if (attrs.type == TYPE_STATUS_BAR_PANEL
-                            || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
-                        // Status bar panels are the only windows who can go on top of
-                        // the status bar.  They are protected by the STATUS_BAR_SERVICE
-                        // permission, so they have the same privileges as the status
-                        // bar itself.
+                    if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
+                        // Status bar panels are the only windows who can go on top of the status
+                        // bar. They are protected by the STATUS_BAR_SERVICE permission, so they
+                        // have the same privileges as the status bar itself.
                         //
                         // However, they should still dodge the navigation bar if it exists.
 
                         pf.left = df.left = of.left = hasNavBar
-                                ? mDockLeft : mUnrestrictedScreenLeft;
-                        pf.top = df.top = of.top = mUnrestrictedScreenTop;
+                                ? displayFrames.mDock.left : displayFrames.mUnrestricted.left;
+                        pf.top = df.top = of.top = displayFrames.mUnrestricted.top;
                         pf.right = df.right = of.right = hasNavBar
-                                ? mRestrictedScreenLeft+mRestrictedScreenWidth
-                                : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
+                                ? displayFrames.mRestricted.right
+                                : displayFrames.mUnrestricted.right;
                         pf.bottom = df.bottom = of.bottom = hasNavBar
-                                ? mRestrictedScreenTop+mRestrictedScreenHeight
-                                : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                                ? displayFrames.mRestricted.bottom
+                                : displayFrames.mUnrestricted.bottom;
 
                         if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                                         "Laying out status bar window: (%d,%d - %d,%d)",
                                         pf.left, pf.top, pf.right, pf.bottom));
                     } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
-                            && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+                            && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
                         // Asking to layout into the overscan region, so give it that pure
                         // unrestricted area.
-                        pf.left = df.left = of.left = mOverscanScreenLeft;
-                        pf.top = df.top = of.top = mOverscanScreenTop;
-                        pf.right = df.right = of.right = mOverscanScreenLeft + mOverscanScreenWidth;
-                        pf.bottom = df.bottom = of.bottom = mOverscanScreenTop
-                                + mOverscanScreenHeight;
+                        of.set(displayFrames.mOverscan);
+                        df.set(displayFrames.mOverscan);
+                        pf.set(displayFrames.mOverscan);
                     } else if (canHideNavigationBar()
                             && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
-                            && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
-                        // Asking for layout as if the nav bar is hidden, lets the
-                        // application extend into the unrestricted overscan screen area.  We
-                        // only do this for application windows to ensure no window that
-                        // can be above the nav bar can do this.
-                        pf.left = df.left = mOverscanScreenLeft;
-                        pf.top = df.top = mOverscanScreenTop;
-                        pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
-                        pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
-                        // We need to tell the app about where the frame inside the overscan
-                        // is, so it can inset its content by that amount -- it didn't ask
-                        // to actually extend itself into the overscan region.
-                        of.left = mUnrestrictedScreenLeft;
-                        of.top = mUnrestrictedScreenTop;
-                        of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                        of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                            && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
+                        // Asking for layout as if the nav bar is hidden, lets the application
+                        // extend into the unrestricted overscan screen area. We only do this for
+                        // application windows to ensure no window that can be above the nav bar can
+                        // do this.
+                        df.set(displayFrames.mOverscan);
+                        pf.set(displayFrames.mOverscan);
+                        // We need to tell the app about where the frame inside the overscan is, so
+                        // it can inset its content by that amount -- it didn't ask to actually
+                        // extend itself into the overscan region.
+                        of.set(displayFrames.mUnrestricted);
                     } else {
-                        pf.left = df.left = mRestrictedOverscanScreenLeft;
-                        pf.top = df.top = mRestrictedOverscanScreenTop;
-                        pf.right = df.right = mRestrictedOverscanScreenLeft
-                                + mRestrictedOverscanScreenWidth;
-                        pf.bottom = df.bottom = mRestrictedOverscanScreenTop
-                                + mRestrictedOverscanScreenHeight;
+                        df.set(displayFrames.mRestrictedOverscan);
+                        pf.set(displayFrames.mRestrictedOverscan);
                         // We need to tell the app about where the frame inside the overscan
                         // is, so it can inset its content by that amount -- it didn't ask
                         // to actually extend itself into the overscan region.
-                        of.left = mUnrestrictedScreenLeft;
-                        of.top = mUnrestrictedScreenTop;
-                        of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                        of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                        of.set(displayFrames.mUnrestricted);
                     }
 
                     if ((fl & FLAG_FULLSCREEN) == 0) {
                         if (win.isVoiceInteraction()) {
-                            cf.left = mVoiceContentLeft;
-                            cf.top = mVoiceContentTop;
-                            cf.right = mVoiceContentRight;
-                            cf.bottom = mVoiceContentBottom;
+                            cf.set(displayFrames.mVoiceContent);
                         } else {
                             if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
-                                cf.left = mDockLeft;
-                                cf.top = mDockTop;
-                                cf.right = mDockRight;
-                                cf.bottom = mDockBottom;
+                                cf.set(displayFrames.mDock);
                             } else {
-                                cf.left = mContentLeft;
-                                cf.top = mContentTop;
-                                cf.right = mContentRight;
-                                cf.bottom = mContentBottom;
+                                cf.set(displayFrames.mContent);
                             }
                         }
                     } else {
-                        // Full screen windows are always given a layout that is as if the
-                        // status bar and other transient decors are gone.  This is to avoid
-                        // bad states when moving from a window that is not hding the
-                        // status bar to one that is.
-                        cf.left = mRestrictedScreenLeft;
-                        cf.top = mRestrictedScreenTop;
-                        cf.right = mRestrictedScreenLeft + mRestrictedScreenWidth;
-                        cf.bottom = mRestrictedScreenTop + mRestrictedScreenHeight;
+                        // Full screen windows are always given a layout that is as if the status
+                        // bar and other transient decors are gone. This is to avoid bad states when
+                        // moving from a window that is not hiding the status bar to one that is.
+                        cf.set(displayFrames.mRestricted);
                     }
-                    applyStableConstraints(sysUiFl, fl, cf);
+                    applyStableConstraints(sysUiFl, fl, cf, displayFrames);
                     if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
-                        vf.left = mCurLeft;
-                        vf.top = mCurTop;
-                        vf.right = mCurRight;
-                        vf.bottom = mCurBottom;
+                        vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
                     }
@@ -5094,86 +4953,70 @@
             } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
                     & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
-                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
-                        "): IN_SCREEN");
+                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+                        + "): IN_SCREEN");
                 // A window that has requested to fill the entire screen just
                 // gets everything, period.
-                if (attrs.type == TYPE_STATUS_BAR_PANEL
-                        || attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
-                    pf.left = df.left = of.left = cf.left = hasNavBar
-                            ? mDockLeft : mUnrestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right = hasNavBar
-                            ? mRestrictedScreenLeft + mRestrictedScreenWidth
-                            : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = hasNavBar
-                            ? mRestrictedScreenTop + mRestrictedScreenHeight
-                            : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
+                    cf.set(displayFrames.mUnrestricted);
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
+                    if (hasNavBar) {
+                        pf.left = df.left = of.left = cf.left = displayFrames.mDock.left;
+                        pf.right = df.right = of.right = cf.right = displayFrames.mRestricted.right;
+                        pf.bottom = df.bottom = of.bottom = cf.bottom =
+                                displayFrames.mRestricted.bottom;
+                    }
                     if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                             "Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
                             pf.left, pf.top, pf.right, pf.bottom));
-                } else if (attrs.type == TYPE_VOLUME_OVERLAY) {
+                } else if (type == TYPE_VOLUME_OVERLAY) {
                     // Volume overlay covers everything, including the status and navbar
-                    pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right =
-                            mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom =
-                            mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+                    cf.set(displayFrames.mUnrestricted);
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
                     if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                                     "Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
                                     pf.left, pf.top, pf.right, pf.bottom));
-                } else if (attrs.type == TYPE_NAVIGATION_BAR
-                        || attrs.type == TYPE_NAVIGATION_BAR_PANEL) {
+                } else if (type == TYPE_NAVIGATION_BAR || type == TYPE_NAVIGATION_BAR_PANEL) {
                     // The navigation bar has Real Ultimate Power.
-                    pf.left = df.left = of.left = mUnrestrictedScreenLeft;
-                    pf.top = df.top = of.top = mUnrestrictedScreenTop;
-                    pf.right = df.right = of.right = mUnrestrictedScreenLeft
-                            + mUnrestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop
-                            + mUnrestrictedScreenHeight;
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
                     if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                                     "Laying out navigation bar window: (%d,%d - %d,%d)",
                                     pf.left, pf.top, pf.right, pf.bottom));
-                } else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY
-                                || attrs.type == TYPE_BOOT_PROGRESS
-                                || attrs.type == TYPE_SCREENSHOT)
+                } else if ((type == TYPE_SECURE_SYSTEM_OVERLAY || type == TYPE_SCREENSHOT)
                         && ((fl & FLAG_FULLSCREEN) != 0)) {
                     // Fullscreen secure system overlays get what they ask for. Screenshot region
                     // selection overlay should also expand to full screen.
-                    pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
-                    pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
-                            + mOverscanScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
-                            + mOverscanScreenHeight;
-                } else if (attrs.type == TYPE_BOOT_PROGRESS) {
+                    cf.set(displayFrames.mOverscan);
+                    of.set(displayFrames.mOverscan);
+                    df.set(displayFrames.mOverscan);
+                    pf.set(displayFrames.mOverscan);
+                } else if (type == TYPE_BOOT_PROGRESS) {
                     // Boot progress screen always covers entire display.
-                    pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
-                    pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
-                            + mOverscanScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
-                            + mOverscanScreenHeight;
+                    cf.set(displayFrames.mOverscan);
+                    of.set(displayFrames.mOverscan);
+                    df.set(displayFrames.mOverscan);
+                    pf.set(displayFrames.mOverscan);
                 } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
-                        && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                        && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
-                    // Asking to layout into the overscan region, so give it that pure
-                    // unrestricted area.
-                    pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
-                    pf.right = df.right = of.right = cf.right
-                            = mOverscanScreenLeft + mOverscanScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom
-                            = mOverscanScreenTop + mOverscanScreenHeight;
+                        && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
+                    // Asking to layout into the overscan region, so give it that pure unrestricted
+                    // area.
+                    cf.set(displayFrames.mOverscan);
+                    of.set(displayFrames.mOverscan);
+                    df.set(displayFrames.mOverscan);
+                    pf.set(displayFrames.mOverscan);
                 } else if (canHideNavigationBar()
                         && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
-                        && (attrs.type == TYPE_STATUS_BAR
-                            || attrs.type == TYPE_TOAST
-                            || attrs.type == TYPE_DOCK_DIVIDER
-                            || attrs.type == TYPE_VOICE_INTERACTION_STARTING
-                            || (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
+                        && (type == TYPE_STATUS_BAR
+                            || type == TYPE_TOAST
+                            || type == TYPE_DOCK_DIVIDER
+                            || type == TYPE_VOICE_INTERACTION_STARTING
+                            || (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW))) {
                     // Asking for layout as if the nav bar is hidden, lets the
                     // application extend into the unrestricted screen area.  We
                     // only do this for application windows (or toasts) to ensure no window that
@@ -5181,102 +5024,76 @@
                     // XXX This assumes that an app asking for this will also
                     // ask for layout in only content.  We can't currently figure out
                     // what the screen would be if only laying out to hide the nav bar.
-                    pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right = mUnrestrictedScreenLeft
-                            + mUnrestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop
-                            + mUnrestrictedScreenHeight;
+                    cf.set(displayFrames.mUnrestricted);
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
                 } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
-                    pf.left = df.left = of.left = mRestrictedScreenLeft;
-                    pf.top = df.top = of.top  = mRestrictedScreenTop;
-                    pf.right = df.right = of.right = mRestrictedScreenLeft + mRestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = mRestrictedScreenTop
-                            + mRestrictedScreenHeight;
+                    of.set(displayFrames.mRestricted);
+                    df.set(displayFrames.mRestricted);
+                    pf.set(displayFrames.mRestricted);
                     if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
-                        cf.left = mDockLeft;
-                        cf.top = mDockTop;
-                        cf.right = mDockRight;
-                        cf.bottom = mDockBottom;
+                        cf.set(displayFrames.mDock);
                     } else {
-                        cf.left = mContentLeft;
-                        cf.top = mContentTop;
-                        cf.right = mContentRight;
-                        cf.bottom = mContentBottom;
+                        cf.set(displayFrames.mContent);
                     }
                 } else {
-                    pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
-                            + mRestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
-                            + mRestrictedScreenHeight;
+                    cf.set(displayFrames.mRestricted);
+                    of.set(displayFrames.mRestricted);
+                    df.set(displayFrames.mRestricted);
+                    pf.set(displayFrames.mRestricted);
                 }
 
-                applyStableConstraints(sysUiFl, fl, cf);
+                applyStableConstraints(sysUiFl, fl, cf,displayFrames);
 
                 if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
-                    vf.left = mCurLeft;
-                    vf.top = mCurTop;
-                    vf.right = mCurRight;
-                    vf.bottom = mCurBottom;
+                    vf.set(displayFrames.mCurrent);
                 } else {
                     vf.set(cf);
                 }
             } else if (attached != null) {
-                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
-                        "): attached to " + attached);
+                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+                        + "): attached to " + attached);
                 // A child window should be placed inside of the same visible
                 // frame that its parent had.
-                setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf);
+                setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf,
+                        displayFrames);
             } else {
                 if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
                         "): normal window");
                 // Otherwise, a normal window must be placed inside the content
                 // of all screen decorations.
-                if (attrs.type == TYPE_STATUS_BAR_PANEL || attrs.type == TYPE_VOLUME_OVERLAY) {
+                if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_VOLUME_OVERLAY) {
                     // Status bar panels and the volume dialog are the only windows who can go on
-                    // top of the status bar.  They are protected by the STATUS_BAR_SERVICE
-                    // permission, so they have the same privileges as the status
-                    // bar itself.
-                    pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
-                    pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
-                    pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
-                            + mRestrictedScreenWidth;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
-                            + mRestrictedScreenHeight;
-                } else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT) {
+                    // top of the status bar. They are protected by the STATUS_BAR_SERVICE
+                    // permission, so they have the same privileges as the status bar itself.
+                    cf.set(displayFrames.mRestricted);
+                    of.set(displayFrames.mRestricted);
+                    df.set(displayFrames.mRestricted);
+                    pf.set(displayFrames.mRestricted);
+                } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
                     // These dialogs are stable to interim decor changes.
-                    pf.left = df.left = of.left = cf.left = mStableLeft;
-                    pf.top = df.top = of.top = cf.top = mStableTop;
-                    pf.right = df.right = of.right = cf.right = mStableRight;
-                    pf.bottom = df.bottom = of.bottom = cf.bottom = mStableBottom;
+                    cf.set(displayFrames.mStable);
+                    of.set(displayFrames.mStable);
+                    df.set(displayFrames.mStable);
+                    pf.set(displayFrames.mStable);
                 } else {
-                    pf.left = mContentLeft;
-                    pf.top = mContentTop;
-                    pf.right = mContentRight;
-                    pf.bottom = mContentBottom;
+                    pf.set(displayFrames.mContent);
                     if (win.isVoiceInteraction()) {
-                        df.left = of.left = cf.left = mVoiceContentLeft;
-                        df.top = of.top = cf.top = mVoiceContentTop;
-                        df.right = of.right = cf.right = mVoiceContentRight;
-                        df.bottom = of.bottom = cf.bottom = mVoiceContentBottom;
+                        cf.set(displayFrames.mVoiceContent);
+                        of.set(displayFrames.mVoiceContent);
+                        df.set(displayFrames.mVoiceContent);
                     } else if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
-                        df.left = of.left = cf.left = mDockLeft;
-                        df.top = of.top = cf.top = mDockTop;
-                        df.right = of.right = cf.right = mDockRight;
-                        df.bottom = of.bottom = cf.bottom = mDockBottom;
+                        cf.set(displayFrames.mDock);
+                        of.set(displayFrames.mDock);
+                        df.set(displayFrames.mDock);
                     } else {
-                        df.left = of.left = cf.left = mContentLeft;
-                        df.top = of.top = cf.top = mContentTop;
-                        df.right = of.right = cf.right = mContentRight;
-                        df.bottom = of.bottom = cf.bottom = mContentBottom;
+                        cf.set(displayFrames.mContent);
+                        of.set(displayFrames.mContent);
+                        df.set(displayFrames.mContent);
                     }
                     if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
-                        vf.left = mCurLeft;
-                        vf.top = mCurTop;
-                        vf.right = mCurRight;
-                        vf.bottom = mCurBottom;
+                        vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
                     }
@@ -5286,11 +5103,11 @@
 
         // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
         // Also, we don't allow windows in multi-window mode to extend out of the screen.
-        if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR
+        if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
                 && !win.isInMultiWindowMode()) {
             df.left = df.top = -10000;
             df.right = df.bottom = 10000;
-            if (attrs.type != TYPE_WALLPAPER) {
+            if (type != TYPE_WALLPAPER) {
                 of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
                 of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
             }
@@ -5306,7 +5123,7 @@
             osf.set(cf.left, cf.top, cf.right, cf.bottom);
             int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
             if (outset > 0) {
-                int rotation = mDisplayRotation;
+                int rotation = displayFrames.mRotation;
                 if (rotation == Surface.ROTATION_0) {
                     osf.bottom += outset;
                 } else if (rotation == Surface.ROTATION_90) {
@@ -5323,7 +5140,7 @@
 
         if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
                 + ": sim=#" + Integer.toHexString(sim)
-                + " attach=" + attached + " type=" + attrs.type
+                + " attach=" + attached + " type=" + type
                 + String.format(" flags=0x%08x", fl)
                 + " pf=" + pf.toShortString() + " df=" + df.toShortString()
                 + " of=" + of.toShortString()
@@ -5336,62 +5153,42 @@
 
         // Dock windows carve out the bottom of the screen, so normal windows
         // can't appear underneath them.
-        if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleLw()
+        if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
                 && !win.getGivenInsetsPendingLw()) {
             setLastInputMethodWindowLw(null, null);
-            offsetInputMethodWindowLw(win);
+            offsetInputMethodWindowLw(win, displayFrames);
         }
-        if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
+        if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
                 && !win.getGivenInsetsPendingLw()) {
-            offsetVoiceInputWindowLw(win);
+            offsetVoiceInputWindowLw(win, displayFrames);
         }
     }
 
-    private void layoutWallpaper(WindowState win, Rect pf, Rect df, Rect of, Rect cf) {
-
-        // The wallpaper also has Real Ultimate Power, but we want to tell
-        // it about the overscan area.
-        pf.left = df.left = mOverscanScreenLeft;
-        pf.top = df.top = mOverscanScreenTop;
-        pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
-        pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
-        of.left = cf.left = mUnrestrictedScreenLeft;
-        of.top = cf.top = mUnrestrictedScreenTop;
-        of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
-        of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
+    private void layoutWallpaper(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect cf) {
+        // The wallpaper has Real Ultimate Power, but we want to tell it about the overscan area.
+        df.set(displayFrames.mOverscan);
+        pf.set(displayFrames.mOverscan);
+        cf.set(displayFrames.mUnrestricted);
+        of.set(displayFrames.mUnrestricted);
     }
 
-    private void offsetInputMethodWindowLw(WindowState win) {
+    private void offsetInputMethodWindowLw(WindowState win, DisplayFrames displayFrames) {
         int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
         top += win.getGivenContentInsetsLw().top;
-        if (mContentBottom > top) {
-            mContentBottom = top;
-        }
-        if (mVoiceContentBottom > top) {
-            mVoiceContentBottom = top;
-        }
+        displayFrames.mContent.bottom = Math.min(displayFrames.mContent.bottom, top);
+        displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
         top = win.getVisibleFrameLw().top;
         top += win.getGivenVisibleInsetsLw().top;
-        if (mCurBottom > top) {
-            mCurBottom = top;
-        }
+        displayFrames.mCurrent.bottom = Math.min(displayFrames.mCurrent.bottom, top);
         if (DEBUG_LAYOUT) Slog.v(TAG, "Input method: mDockBottom="
-                + mDockBottom + " mContentBottom="
-                + mContentBottom + " mCurBottom=" + mCurBottom);
+                + displayFrames.mDock.bottom + " mContentBottom="
+                + displayFrames.mContent.bottom + " mCurBottom=" + displayFrames.mCurrent.bottom);
     }
 
-    private void offsetVoiceInputWindowLw(WindowState win) {
+    private void offsetVoiceInputWindowLw(WindowState win, DisplayFrames displayFrames) {
         int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
         top += win.getGivenContentInsetsLw().top;
-        if (mVoiceContentBottom > top) {
-            mVoiceContentBottom = top;
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void finishLayoutLw() {
-        return;
+        displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
     }
 
     /** {@inheritDoc} */
@@ -5798,6 +5595,15 @@
     }
 
     void initializeHdmiState() {
+        final int oldMask = StrictMode.allowThreadDiskReadsMask();
+        try {
+            initializeHdmiStateInternal();
+        } finally {
+            StrictMode.setThreadPolicyMask(oldMask);
+        }
+    }
+
+    void initializeHdmiStateInternal() {
         boolean plugged = false;
         // watch for HDMI plug messages if the hdmi switch exists
         if (new File("/sys/devices/virtual/switch/hdmi/state").exists()) {
@@ -8235,11 +8041,6 @@
     }
 
     @Override
-    public int getInputMethodWindowVisibleHeightLw() {
-        return mDockBottom - mCurBottom;
-    }
-
-    @Override
     public void setCurrentUserLw(int newUserId) {
         mCurrentUserId = newUserId;
         if (mKeyguardDelegate != null) {
@@ -8328,8 +8129,6 @@
     @Override
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        new Rect(mStableLeft, mStableTop, mStableRight, mStableBottom).writeToProto(proto,
-                STABLE_BOUNDS);
         proto.end(token);
     }
 
@@ -8435,58 +8234,6 @@
                 pw.print(" mScreenOnFully="); pw.println(mScreenOnFully);
         pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
                 pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
-        pw.print(prefix); pw.print("mOverscanScreen=("); pw.print(mOverscanScreenLeft);
-                pw.print(","); pw.print(mOverscanScreenTop);
-                pw.print(") "); pw.print(mOverscanScreenWidth);
-                pw.print("x"); pw.println(mOverscanScreenHeight);
-        if (mOverscanLeft != 0 || mOverscanTop != 0
-                || mOverscanRight != 0 || mOverscanBottom != 0) {
-            pw.print(prefix); pw.print("mOverscan left="); pw.print(mOverscanLeft);
-                    pw.print(" top="); pw.print(mOverscanTop);
-                    pw.print(" right="); pw.print(mOverscanRight);
-                    pw.print(" bottom="); pw.println(mOverscanBottom);
-        }
-        pw.print(prefix); pw.print("mRestrictedOverscanScreen=(");
-                pw.print(mRestrictedOverscanScreenLeft);
-                pw.print(","); pw.print(mRestrictedOverscanScreenTop);
-                pw.print(") "); pw.print(mRestrictedOverscanScreenWidth);
-                pw.print("x"); pw.println(mRestrictedOverscanScreenHeight);
-        pw.print(prefix); pw.print("mUnrestrictedScreen=("); pw.print(mUnrestrictedScreenLeft);
-                pw.print(","); pw.print(mUnrestrictedScreenTop);
-                pw.print(") "); pw.print(mUnrestrictedScreenWidth);
-                pw.print("x"); pw.println(mUnrestrictedScreenHeight);
-        pw.print(prefix); pw.print("mRestrictedScreen=("); pw.print(mRestrictedScreenLeft);
-                pw.print(","); pw.print(mRestrictedScreenTop);
-                pw.print(") "); pw.print(mRestrictedScreenWidth);
-                pw.print("x"); pw.println(mRestrictedScreenHeight);
-        pw.print(prefix); pw.print("mStableFullscreen=("); pw.print(mStableFullscreenLeft);
-                pw.print(","); pw.print(mStableFullscreenTop);
-                pw.print(")-("); pw.print(mStableFullscreenRight);
-                pw.print(","); pw.print(mStableFullscreenBottom); pw.println(")");
-        pw.print(prefix); pw.print("mStable=("); pw.print(mStableLeft);
-                pw.print(","); pw.print(mStableTop);
-                pw.print(")-("); pw.print(mStableRight);
-                pw.print(","); pw.print(mStableBottom); pw.println(")");
-        pw.print(prefix); pw.print("mSystem=("); pw.print(mSystemLeft);
-                pw.print(","); pw.print(mSystemTop);
-                pw.print(")-("); pw.print(mSystemRight);
-                pw.print(","); pw.print(mSystemBottom); pw.println(")");
-        pw.print(prefix); pw.print("mCur=("); pw.print(mCurLeft);
-                pw.print(","); pw.print(mCurTop);
-                pw.print(")-("); pw.print(mCurRight);
-                pw.print(","); pw.print(mCurBottom); pw.println(")");
-        pw.print(prefix); pw.print("mContent=("); pw.print(mContentLeft);
-                pw.print(","); pw.print(mContentTop);
-                pw.print(")-("); pw.print(mContentRight);
-                pw.print(","); pw.print(mContentBottom); pw.println(")");
-        pw.print(prefix); pw.print("mVoiceContent=("); pw.print(mVoiceContentLeft);
-                pw.print(","); pw.print(mVoiceContentTop);
-                pw.print(")-("); pw.print(mVoiceContentRight);
-                pw.print(","); pw.print(mVoiceContentBottom); pw.println(")");
-        pw.print(prefix); pw.print("mDock=("); pw.print(mDockLeft);
-                pw.print(","); pw.print(mDockTop);
-                pw.print(")-("); pw.print(mDockRight);
-                pw.print(","); pw.print(mDockBottom); pw.println(")");
         pw.print(prefix); pw.print("mDockLayer="); pw.print(mDockLayer);
                 pw.print(" mStatusBarLayer="); pw.println(mStatusBarLayer);
         pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream);
diff --git a/com/android/server/power/BatterySaverPolicy.java b/com/android/server/power/BatterySaverPolicy.java
index 1781d8c..15121b8 100644
--- a/com/android/server/power/BatterySaverPolicy.java
+++ b/com/android/server/power/BatterySaverPolicy.java
@@ -15,50 +15,33 @@
  */
 package com.android.server.power;
 
-import android.annotation.IntDef;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.PowerManager.ServiceType;
+import android.os.PowerSaveState;
 import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.KeyValueListParser;
 import android.util.Slog;
-import android.os.PowerSaveState;
+
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 
 /**
  * Class to decide whether to turn on battery saver mode for specific service
+ *
+ * Test: atest BatterySaverPolicyTest
  */
 public class BatterySaverPolicy extends ContentObserver {
-    @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})
-    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;
-    }
-
     private static final String TAG = "BatterySaverPolicy";
 
     // Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode.
@@ -79,8 +62,16 @@
     private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
     private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
     private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
+    private static final String KEY_FORCE_ALL_APPS_STANDBY_JOBS = "force_all_apps_standby_jobs";
+    private static final String KEY_FORCE_ALL_APPS_STANDBY_ALARMS = "force_all_apps_standby_alarms";
+    private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
 
-    private final KeyValueListParser mParser = new KeyValueListParser(',');
+    private static final String KEY_SCREEN_ON_FILE_PREFIX = "file-on:";
+    private static final String KEY_SCREEN_OFF_FILE_PREFIX = "file-off:";
+
+    private static String mSettings;
+    private static String mDeviceSpecificSettings;
+    private static String mDeviceSpecificSettingsSource; // For dump() only.
 
     /**
      * {@code true} if vibration is disabled in battery saver mode.
@@ -164,51 +155,189 @@
      */
     private float mAdjustBrightnessFactor;
 
+    /**
+     * Whether to put all apps in the stand-by mode or not for job scheduler.
+     */
+    private boolean mForceAllAppsStandbyJobs;
+
+    /**
+     * Whether to put all apps in the stand-by mode or not for alarms.
+     */
+    private boolean mForceAllAppsStandbyAlarms;
+
+    /**
+     * Weather to show non-essential sensors (e.g. edge sensors) or not.
+     */
+    private boolean mOptionalSensorsDisabled;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private Context mContext;
+
+    @GuardedBy("mLock")
     private ContentResolver mContentResolver;
 
+    @GuardedBy("mLock")
+    private final ArrayList<BatterySaverPolicyListener> mListeners = new ArrayList<>();
+
+    /**
+     * List of [Filename -> content] that should be written when battery saver is activated
+     * and the screen is on.
+     *
+     * We use this to change the max CPU frequencies.
+     */
+    @GuardedBy("mLock")
+    private ArrayMap<String, String> mScreenOnFiles;
+
+    /**
+     * List of [Filename -> content] that should be written when battery saver is activated
+     * and the screen is off.
+     *
+     * We use this to change the max CPU frequencies.
+     */
+    @GuardedBy("mLock")
+    private ArrayMap<String, String> mScreenOffFiles;
+
+    public interface BatterySaverPolicyListener {
+        void onBatterySaverPolicyChanged(BatterySaverPolicy policy);
+    }
+
     public BatterySaverPolicy(Handler handler) {
         super(handler);
     }
 
-    public void start(ContentResolver contentResolver) {
-        mContentResolver = contentResolver;
+    public void systemReady(Context context) {
+        synchronized (mLock) {
+            mContext = context;
+            mContentResolver = context.getContentResolver();
 
-        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
-                Settings.Global.BATTERY_SAVER_CONSTANTS), false, this);
+            mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.BATTERY_SAVER_CONSTANTS), false, this);
+            mContentResolver.registerContentObserver(Settings.Global.getUriFor(
+                    Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS), false, this);
+        }
+        onChange(true, null);
+    }
+
+    public void addListener(BatterySaverPolicyListener listener) {
+        synchronized (mLock) {
+            mListeners.add(listener);
+        }
+    }
+
+    @VisibleForTesting
+    String getGlobalSetting(String key) {
+        return Settings.Global.getString(mContentResolver, key);
+    }
+
+    @VisibleForTesting
+    int getDeviceSpecificConfigResId() {
+        return R.string.config_batterySaverDeviceSpecificConfig;
+    }
+
+    @VisibleForTesting
+    void onChangeForTest() {
         onChange(true, null);
     }
 
     @Override
     public void onChange(boolean selfChange, Uri uri) {
-        final String value = Settings.Global.getString(mContentResolver,
-                Settings.Global.BATTERY_SAVER_CONSTANTS);
-        updateConstants(value);
+        final BatterySaverPolicyListener[] listeners;
+        synchronized (mLock) {
+            // Load the non-device-specific setting.
+            final String setting = getGlobalSetting(Settings.Global.BATTERY_SAVER_CONSTANTS);
+
+            // Load the device specific setting.
+            // We first check the global setting, and if it's empty or the string "null" is set,
+            // use the default value from config.xml.
+            String deviceSpecificSetting = getGlobalSetting(
+                    Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS);
+            mDeviceSpecificSettingsSource =
+                    Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS;
+
+            if (TextUtils.isEmpty(deviceSpecificSetting) || "null".equals(deviceSpecificSetting)) {
+                deviceSpecificSetting =
+                        mContext.getString(getDeviceSpecificConfigResId());
+                mDeviceSpecificSettingsSource = "(overlay)";
+            }
+
+            // Update.
+            updateConstantsLocked(setting, deviceSpecificSetting);
+
+            listeners = mListeners.toArray(new BatterySaverPolicyListener[mListeners.size()]);
+        }
+
+        // Notify the listeners.
+        for (BatterySaverPolicyListener listener : listeners) {
+            listener.onBatterySaverPolicyChanged(this);
+        }
     }
 
     @VisibleForTesting
-    void updateConstants(final String value) {
-        synchronized (BatterySaverPolicy.this) {
-            try {
-                mParser.setString(value);
-            } catch (IllegalArgumentException e) {
-                Slog.e(TAG, "Bad battery saver constants");
+    void updateConstantsLocked(final String setting, final String deviceSpecificSetting) {
+        mSettings = setting;
+        mDeviceSpecificSettings = deviceSpecificSetting;
+
+        final KeyValueListParser parser = new KeyValueListParser(',');
+
+        // Non-device-specific parameters.
+        try {
+            parser.setString(setting);
+        } catch (IllegalArgumentException e) {
+            Slog.wtf(TAG, "Bad battery saver constants: " + setting);
+        }
+
+        mVibrationDisabled = parser.getBoolean(KEY_VIBRATION_DISABLED, true);
+        mAnimationDisabled = parser.getBoolean(KEY_ANIMATION_DISABLED, true);
+        mSoundTriggerDisabled = parser.getBoolean(KEY_SOUNDTRIGGER_DISABLED, true);
+        mFullBackupDeferred = parser.getBoolean(KEY_FULLBACKUP_DEFERRED, true);
+        mKeyValueBackupDeferred = parser.getBoolean(KEY_KEYVALUE_DEFERRED, true);
+        mFireWallDisabled = parser.getBoolean(KEY_FIREWALL_DISABLED, false);
+        mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
+        mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
+        mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
+        mForceAllAppsStandbyJobs = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_JOBS, true);
+        mForceAllAppsStandbyAlarms =
+                parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_ALARMS, true);
+        mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
+
+        // Get default value from Settings.Secure
+        final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
+                GPS_MODE_NO_CHANGE);
+        mGpsMode = parser.getInt(KEY_GPS_MODE, defaultGpsMode);
+
+        // Non-device-specific parameters.
+        try {
+            parser.setString(deviceSpecificSetting);
+        } catch (IllegalArgumentException e) {
+            Slog.wtf(TAG, "Bad device specific battery saver constants: "
+                    + deviceSpecificSetting);
+        }
+
+        mScreenOnFiles = collectParams(parser, KEY_SCREEN_ON_FILE_PREFIX);
+        mScreenOffFiles = collectParams(parser, KEY_SCREEN_OFF_FILE_PREFIX);
+    }
+
+    private static ArrayMap<String, String> collectParams(
+            KeyValueListParser parser, String prefix) {
+        final ArrayMap<String, String> ret = new ArrayMap<>();
+
+        for (int i = parser.size() - 1; i >= 0; i--) {
+            final String key = parser.keyAt(i);
+            if (!key.startsWith(prefix)) {
+                continue;
+            }
+            final String path = key.substring(prefix.length());
+
+            if (!(path.startsWith("/sys/") || path.startsWith("/proc"))) {
+                Slog.wtf(TAG, "Invalid path: " + path);
+                continue;
             }
 
-            mVibrationDisabled = mParser.getBoolean(KEY_VIBRATION_DISABLED, true);
-            mAnimationDisabled = mParser.getBoolean(KEY_ANIMATION_DISABLED, true);
-            mSoundTriggerDisabled = mParser.getBoolean(KEY_SOUNDTRIGGER_DISABLED, true);
-            mFullBackupDeferred = mParser.getBoolean(KEY_FULLBACKUP_DEFERRED, true);
-            mKeyValueBackupDeferred = mParser.getBoolean(KEY_KEYVALUE_DEFERRED, true);
-            mFireWallDisabled = mParser.getBoolean(KEY_FIREWALL_DISABLED, false);
-            mAdjustBrightnessDisabled = mParser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
-            mAdjustBrightnessFactor = mParser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
-            mDataSaverDisabled = mParser.getBoolean(KEY_DATASAVER_DISABLED, true);
-
-            // Get default value from Settings.Secure
-            final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
-                    GPS_MODE_DISABLED_WHEN_SCREEN_OFF);
-            mGpsMode = mParser.getInt(KEY_GPS_MODE, defaultGpsMode);
+            ret.put(path, parser.getString(key, ""));
         }
+        return ret;
     }
 
     /**
@@ -221,7 +350,7 @@
      * @return State data that contains battery saver data
      */
     public PowerSaveState getBatterySaverPolicy(@ServiceType int type, boolean realMode) {
-        synchronized (BatterySaverPolicy.this) {
+        synchronized (mLock) {
             final PowerSaveState.Builder builder = new PowerSaveState.Builder()
                     .setGlobalBatterySaverEnabled(realMode);
             if (!realMode) {
@@ -258,6 +387,15 @@
                 case ServiceType.VIBRATION:
                     return builder.setBatterySaverEnabled(mVibrationDisabled)
                             .build();
+                case ServiceType.FORCE_ALL_APPS_STANDBY_JOBS:
+                    return builder.setBatterySaverEnabled(mForceAllAppsStandbyJobs)
+                            .build();
+                case ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS:
+                    return builder.setBatterySaverEnabled(mForceAllAppsStandbyAlarms)
+                            .build();
+                case ServiceType.OPTIONAL_SENSORS:
+                    return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
+                            .build();
                 default:
                     return builder.setBatterySaverEnabled(realMode)
                             .build();
@@ -265,23 +403,57 @@
         }
     }
 
+    public ArrayMap<String, String> getFileValues(boolean screenOn) {
+        synchronized (mLock) {
+            return screenOn ? mScreenOnFiles : mScreenOffFiles;
+        }
+    }
+
     public void dump(PrintWriter pw) {
-        pw.println();
-        pw.println("Battery saver policy");
-        pw.println("  Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
-        pw.println("  value: " + Settings.Global.getString(mContentResolver,
-                Settings.Global.BATTERY_SAVER_CONSTANTS));
+        synchronized (mLock) {
+            pw.println();
+            pw.println("Battery saver policy");
+            pw.println("  Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
+            pw.println("  value: " + mSettings);
+            pw.println("  Settings " + mDeviceSpecificSettingsSource);
+            pw.println("  value: " + mDeviceSpecificSettings);
 
-        pw.println();
-        pw.println("  " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
-        pw.println("  " + KEY_ANIMATION_DISABLED + "=" + mAnimationDisabled);
-        pw.println("  " + KEY_FULLBACKUP_DEFERRED + "=" + mFullBackupDeferred);
-        pw.println("  " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
-        pw.println("  " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
-        pw.println("  " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
-        pw.println("  " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
-        pw.println("  " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
-        pw.println("  " + KEY_GPS_MODE + "=" + mGpsMode);
+            pw.println();
+            pw.println("  " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
+            pw.println("  " + KEY_ANIMATION_DISABLED + "=" + mAnimationDisabled);
+            pw.println("  " + KEY_FULLBACKUP_DEFERRED + "=" + mFullBackupDeferred);
+            pw.println("  " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
+            pw.println("  " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
+            pw.println("  " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
+            pw.println("  " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
+            pw.println("  " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
+            pw.println("  " + KEY_GPS_MODE + "=" + mGpsMode);
+            pw.println("  " + KEY_FORCE_ALL_APPS_STANDBY_JOBS + "=" + mForceAllAppsStandbyJobs);
+            pw.println("  " + KEY_FORCE_ALL_APPS_STANDBY_ALARMS + "=" + mForceAllAppsStandbyAlarms);
+            pw.println("  " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
+            pw.println();
 
+            pw.print("  Screen On Files:\n");
+            dumpMap(pw, "    ", mScreenOnFiles);
+            pw.println();
+
+            pw.print("  Screen Off Files:\n");
+            dumpMap(pw, "    ", mScreenOffFiles);
+            pw.println();
+        }
+    }
+
+    private void dumpMap(PrintWriter pw, String prefix, ArrayMap<String, String> map) {
+        if (map == null) {
+            return;
+        }
+        final int size = map.size();
+        for (int i = 0; i < size; i++) {
+            pw.print(prefix);
+            pw.print(map.keyAt(i));
+            pw.print(": '");
+            pw.print(map.valueAt(i));
+            pw.println("'");
+        }
     }
 }
diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java
index 2494bde..a47b809 100644
--- a/com/android/server/power/PowerManagerService.java
+++ b/com/android/server/power/PowerManagerService.java
@@ -43,6 +43,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.Process;
@@ -62,7 +63,6 @@
 import android.service.vr.IVrStateCallbacks;
 import android.util.EventLog;
 import android.util.KeyValueListParser;
-import android.util.Log;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -70,6 +70,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 import android.view.WindowManagerPolicy;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
@@ -89,11 +90,8 @@
 import com.android.server.am.BatteryStatsService;
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
+import com.android.server.power.batterysaver.BatterySaverController;
+
 import libcore.util.Objects;
 
 import java.io.FileDescriptor;
@@ -226,6 +224,7 @@
     private final PowerManagerHandler mHandler;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     private final BatterySaverPolicy mBatterySaverPolicy;
+    private final BatterySaverController mBatterySaverController;
 
     private LightsManager mLightsManager;
     private BatteryManagerInternal mBatteryManagerInternal;
@@ -553,9 +552,6 @@
     // True if double tap to wake is enabled
     private boolean mDoubleTapWakeEnabled;
 
-    private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners
-            = new ArrayList<PowerManagerInternal.LowPowerModeListener>();
-
     // True if we are currently in VR Mode.
     private boolean mIsVrModeEnabled;
 
@@ -643,7 +639,10 @@
         mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
         mConstants = new Constants(mHandler);
         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
+
         mBatterySaverPolicy = new BatterySaverPolicy(mHandler);
+        mBatterySaverController = new BatterySaverController(mContext,
+                BackgroundThread.get().getLooper(), mBatterySaverPolicy);
 
         synchronized (mLock) {
             mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
@@ -668,7 +667,6 @@
     PowerManagerService(Context context, BatterySaverPolicy batterySaverPolicy) {
         super(context);
 
-        mBatterySaverPolicy = batterySaverPolicy;
         mContext = context;
         mHandlerThread = new ServiceThread(TAG,
                 Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
@@ -678,6 +676,10 @@
         mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
         mDisplaySuspendBlocker = null;
         mWakeLockSuspendBlocker = null;
+
+        mBatterySaverPolicy = batterySaverPolicy;
+        mBatterySaverController = new BatterySaverController(context,
+                BackgroundThread.getHandler().getLooper(), batterySaverPolicy);
     }
 
     @Override
@@ -750,6 +752,7 @@
             mDisplayManagerInternal.initPowerManagement(
                     mDisplayPowerCallbacks, mHandler, sensorManager);
 
+
             // Go.
             readConfigurationLocked();
             updateSettingsLocked();
@@ -759,7 +762,9 @@
 
         final ContentResolver resolver = mContext.getContentResolver();
         mConstants.start(resolver);
-        mBatterySaverPolicy.start(resolver);
+
+        mBatterySaverController.systemReady();
+        mBatterySaverPolicy.systemReady(mContext);
 
         // Register for settings changes.
         resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -994,36 +999,9 @@
 
         if (mLowPowerModeEnabled != lowPowerModeEnabled) {
             mLowPowerModeEnabled = lowPowerModeEnabled;
-            powerHintInternal(PowerHint.LOW_POWER, lowPowerModeEnabled ? 1 : 0);
-            postAfterBootCompleted(new Runnable() {
-                @Override
-                public void run() {
-                    Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
-                            .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, mLowPowerModeEnabled)
-                            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    mContext.sendBroadcast(intent);
-                    ArrayList<PowerManagerInternal.LowPowerModeListener> listeners;
-                    synchronized (mLock) {
-                        listeners = new ArrayList<PowerManagerInternal.LowPowerModeListener>(
-                                mLowPowerModeListeners);
-                    }
-                    for (int i = 0; i < listeners.size(); i++) {
-                        final PowerManagerInternal.LowPowerModeListener listener = listeners.get(i);
-                        final PowerSaveState result =
-                                mBatterySaverPolicy.getBatterySaverPolicy(
-                                        listener.getServiceType(), lowPowerModeEnabled);
-                        listener.onLowPowerModeChanged(result);
-                    }
-                    intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-                    // Send internal version that requires signature permission.
-                    intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
-                            Manifest.permission.DEVICE_POWER);
-                }
-            });
+
+            postAfterBootCompleted(() ->
+                    mBatterySaverController.enableBatterySaver(mLowPowerModeEnabled));
         }
     }
 
@@ -2364,9 +2342,13 @@
 
             if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
                 mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
-                if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
-                        && (mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
-                    mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+                if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+                    if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
+                        mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+                    }
+                    if (mDisplayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
+                        mDisplayPowerRequest.dozeScreenState = Display.STATE_ON;
+                    }
                 }
                 mDisplayPowerRequest.dozeScreenBrightness =
                         mDozeScreenBrightnessOverrideFromDreamManager;
@@ -3123,7 +3105,7 @@
         mIsVrModeEnabled = enabled;
     }
 
-    private void powerHintInternal(int hintId, int data) {
+    public static void powerHintInternal(int hintId, int data) {
         nativeSendPowerHint(hintId, data);
     }
 
@@ -4392,7 +4374,7 @@
          * Gets the reason for the last time the phone had to reboot.
          *
          * @return The reason the phone last shut down as an int or
-         * {@link PowerManager.SHUTDOWN_REASON_UNKNOWN} if the file could not be opened.
+         * {@link PowerManager#SHUTDOWN_REASON_UNKNOWN} if the file could not be opened.
          */
         @Override // Binder call
         public int getLastShutdownReason() {
@@ -4676,6 +4658,7 @@
                 case Display.STATE_OFF:
                 case Display.STATE_DOZE:
                 case Display.STATE_DOZE_SUSPEND:
+                case Display.STATE_ON_SUSPEND:
                 case Display.STATE_ON:
                 case Display.STATE_VR:
                     break;
@@ -4714,9 +4697,7 @@
 
         @Override
         public void registerLowPowerModeObserver(LowPowerModeListener listener) {
-            synchronized (mLock) {
-                mLowPowerModeListeners.add(listener);
-            }
+            mBatterySaverController.addListener(listener);
         }
 
         @Override
diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java
index 755c5f0..6bf725e 100644
--- a/com/android/server/power/ShutdownThread.java
+++ b/com/android/server/power/ShutdownThread.java
@@ -68,7 +68,8 @@
 public final class ShutdownThread extends Thread {
     // constants
     private static final String TAG = "ShutdownThread";
-    private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
+    private static final int ACTION_DONE_POLL_WAIT_MS = 500;
+    private static final int RADIOS_STATE_POLL_SLEEP_MS = 100;
     // maximum time we wait for the shutdown broadcast before going on.
     private static final int MAX_BROADCAST_TIME = 10*1000;
     private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
@@ -471,7 +472,7 @@
                     sInstance.setRebootProgress(status, null);
                 }
                 try {
-                    mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
+                    mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
                 } catch (InterruptedException e) {
                 }
             }
@@ -565,7 +566,7 @@
                     sInstance.setRebootProgress(status, null);
                 }
                 try {
-                    mActionDoneSync.wait(Math.min(delay, PHONE_STATE_POLL_SLEEP_MSEC));
+                    mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
                 } catch (InterruptedException e) {
                 }
             }
@@ -710,8 +711,7 @@
                         done[0] = true;
                         break;
                     }
-                    SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
-
+                    SystemClock.sleep(RADIOS_STATE_POLL_SLEEP_MS);
                     delay = endTime - SystemClock.elapsedRealtime();
                 }
             }
diff --git a/com/android/server/power/batterysaver/BatterySaverController.java b/com/android/server/power/batterysaver/BatterySaverController.java
new file mode 100644
index 0000000..b3e8538
--- /dev/null
+++ b/com/android/server/power/batterysaver/BatterySaverController.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.batterysaver;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.power.V1_0.PowerHint;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal.LowPowerModeListener;
+import android.os.PowerSaveState;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.widget.Toast;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.server.power.BatterySaverPolicy;
+import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
+import com.android.server.power.PowerManagerService;
+
+import java.util.ArrayList;
+
+/**
+ * Responsible for battery saver mode transition logic.
+ */
+public class BatterySaverController implements BatterySaverPolicyListener {
+    static final String TAG = "BatterySaverController";
+
+    static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final MyHandler mHandler;
+    private final FileUpdater mFileUpdater;
+
+    private PowerManager mPowerManager;
+
+    private final BatterySaverPolicy mBatterySaverPolicy;
+
+    @GuardedBy("mLock")
+    private final ArrayList<LowPowerModeListener> mListeners = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private boolean mEnabled;
+
+    /**
+     * Keep track of the previous enabled state, which we use to decide when to send broadcasts,
+     * which we don't want to send only when the screen state changes.
+     */
+    @GuardedBy("mLock")
+    private boolean mWasEnabled;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case Intent.ACTION_SCREEN_ON:
+                case Intent.ACTION_SCREEN_OFF:
+                    mHandler.postStateChanged();
+                    break;
+            }
+        }
+    };
+
+    /**
+     * Constructor.
+     */
+    public BatterySaverController(Context context, Looper looper, BatterySaverPolicy policy) {
+        mContext = context;
+        mHandler = new MyHandler(looper);
+        mBatterySaverPolicy = policy;
+        mBatterySaverPolicy.addListener(this);
+        mFileUpdater = new FileUpdater(context);
+    }
+
+    /**
+     * Add a listener.
+     */
+    public void addListener(LowPowerModeListener listener) {
+        synchronized (mLock) {
+            mListeners.add(listener);
+        }
+    }
+
+    /**
+     * Called by {@link PowerManagerService} on system ready..
+     */
+    public void systemReady() {
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        mContext.registerReceiver(mReceiver, filter);
+    }
+
+    private PowerManager getPowerManager() {
+        if (mPowerManager == null) {
+            mPowerManager =
+                    Preconditions.checkNotNull(mContext.getSystemService(PowerManager.class));
+        }
+        return mPowerManager;
+    }
+
+    @Override
+    public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) {
+        mHandler.postStateChanged();
+    }
+
+    private class MyHandler extends Handler {
+        private final int MSG_STATE_CHANGED = 1;
+
+        public MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        public void postStateChanged() {
+            obtainMessage(MSG_STATE_CHANGED).sendToTarget();
+        }
+
+        @Override
+        public void dispatchMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_STATE_CHANGED:
+                    handleBatterySaverStateChanged();
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Called by {@link PowerManagerService} to update the battery saver stete.
+     */
+    public void enableBatterySaver(boolean enable) {
+        synchronized (mLock) {
+            if (mEnabled == enable) {
+                return;
+            }
+            mEnabled = enable;
+
+            mHandler.postStateChanged();
+        }
+    }
+
+    /**
+     * Dispatch power save events to the listeners.
+     *
+     * This is always called on the handler thread.
+     */
+    void handleBatterySaverStateChanged() {
+        final LowPowerModeListener[] listeners;
+
+        final boolean wasEnabled;
+        final boolean enabled;
+        final boolean isScreenOn = getPowerManager().isInteractive();
+        final ArrayMap<String, String> fileValues;
+
+        synchronized (mLock) {
+            Slog.i(TAG, "Battery saver enabled: screen on=" + isScreenOn);
+
+            listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
+            wasEnabled = mWasEnabled;
+            enabled = mEnabled;
+
+            if (enabled) {
+                fileValues = mBatterySaverPolicy.getFileValues(isScreenOn);
+            } else {
+                fileValues = null;
+            }
+        }
+
+        PowerManagerService.powerHintInternal(PowerHint.LOW_POWER, enabled ? 1 : 0);
+
+        if (enabled) {
+            // STOPSHIP Remove the toast.
+            Toast.makeText(mContext,
+                    com.android.internal.R.string.battery_saver_warning,
+                    Toast.LENGTH_LONG).show();
+        }
+
+        if (fileValues == null || fileValues.size() == 0) {
+            mFileUpdater.restoreDefault();
+        } else {
+            mFileUpdater.writeFiles(fileValues);
+        }
+
+        if (enabled != wasEnabled) {
+            if (DEBUG) {
+                Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
+            }
+
+            // Send the broadcasts and notify the listeners. We only do this when the battery saver
+            // mode changes, but not when only the screen state changes.
+            Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
+                    .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, enabled)
+                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            mContext.sendBroadcast(intent);
+
+            intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+            // Send internal version that requires signature permission.
+            intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    Manifest.permission.DEVICE_POWER);
+
+
+            for (LowPowerModeListener listener : listeners) {
+                final PowerSaveState result =
+                        mBatterySaverPolicy.getBatterySaverPolicy(
+                                listener.getServiceType(), enabled);
+                listener.onLowPowerModeChanged(result);
+            }
+        }
+
+        synchronized (mLock) {
+            mWasEnabled = enabled;
+        }
+    }
+}
diff --git a/com/android/server/power/batterysaver/FileUpdater.java b/com/android/server/power/batterysaver/FileUpdater.java
new file mode 100644
index 0000000..cfe8fc4
--- /dev/null
+++ b/com/android/server/power/batterysaver/FileUpdater.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.power.batterysaver;
+
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+/**
+ * Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files
+ * with retry and to restore the original values.
+ *
+ * TODO Implement it
+ */
+public class FileUpdater {
+    private static final String TAG = BatterySaverController.TAG;
+
+    private static final boolean DEBUG = BatterySaverController.DEBUG;
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+
+    public FileUpdater(Context context) {
+        mContext = context;
+    }
+
+    public void writeFiles(ArrayMap<String, String> fileValues) {
+        if (DEBUG) {
+            final int size = fileValues.size();
+            for (int i = 0; i < size; i++) {
+                Slog.d(TAG, "Writing '" + fileValues.valueAt(i)
+                        + "' to '" + fileValues.keyAt(i) + "'");
+            }
+        }
+    }
+
+    public void restoreDefault() {
+        if (DEBUG) {
+            Slog.d(TAG, "Resetting file default values");
+        }
+    }
+}
diff --git a/com/android/server/print/UserState.java b/com/android/server/print/UserState.java
index 58833be..e8ae020 100644
--- a/com/android/server/print/UserState.java
+++ b/com/android/server/print/UserState.java
@@ -597,6 +597,7 @@
                 PrintJobStateChangeListenerRecord record =
                         mPrintJobStateChangeListenerRecords.get(i);
                 if (record.listener.asBinder().equals(listener.asBinder())) {
+                    record.destroy();
                     mPrintJobStateChangeListenerRecords.remove(i);
                     break;
                 }
@@ -639,6 +640,7 @@
                 ListenerRecord<IPrintServicesChangeListener> record =
                         mPrintServicesChangeListenerRecords.get(i);
                 if (record.listener.asBinder().equals(listener.asBinder())) {
+                    record.destroy();
                     mPrintServicesChangeListenerRecords.remove(i);
                     break;
                 }
@@ -686,6 +688,7 @@
                 ListenerRecord<IRecommendationsChangeListener> record =
                         mPrintServiceRecommendationsChangeListenerRecords.get(i);
                 if (record.listener.asBinder().equals(listener.asBinder())) {
+                    record.destroy();
                     mPrintServiceRecommendationsChangeListenerRecords.remove(i);
                     break;
                 }
@@ -1285,6 +1288,10 @@
             listener.asBinder().linkToDeath(this, 0);
         }
 
+        public void destroy() {
+            listener.asBinder().unlinkToDeath(this, 0);
+        }
+
         @Override
         public void binderDied() {
             listener.asBinder().unlinkToDeath(this, 0);
@@ -1302,6 +1309,10 @@
             listener.asBinder().linkToDeath(this, 0);
         }
 
+        public void destroy() {
+            listener.asBinder().unlinkToDeath(this, 0);
+        }
+
         @Override
         public void binderDied() {
             listener.asBinder().unlinkToDeath(this, 0);
diff --git a/com/android/server/soundtrigger/SoundTriggerHelper.java b/com/android/server/soundtrigger/SoundTriggerHelper.java
index f53eb15..25c54b3 100644
--- a/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -36,12 +36,12 @@
 import android.hardware.soundtrigger.SoundTriggerModule;
 import android.os.DeadObjectException;
 import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
 import android.os.RemoteException;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.util.Slog;
 import com.android.internal.logging.MetricsLogger;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
index 22d2bcf..dafab77 100644
--- a/com/android/server/stats/StatsCompanionService.java
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -24,7 +24,8 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.content.IntentFilter;
+import android.net.NetworkStats;
+import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -37,15 +38,19 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Slog;
+import android.util.StatsLog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.internal.os.KernelCpuSpeedReader;
+import com.android.internal.os.PowerProfile;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
 
 import java.util.ArrayList;
 import java.util.List;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.KernelWakelockReader;
-import com.android.internal.os.KernelWakelockStats;
-import com.android.server.SystemService;
-
 import java.util.Map;
 
 /**
@@ -57,6 +62,8 @@
 public class StatsCompanionService extends IStatsCompanionService.Stub {
     static final String TAG = "StatsCompanionService";
     static final boolean DEBUG = true;
+    public static final String ACTION_TRIGGER_COLLECTION =
+        "com.android.server.stats.action.TRIGGER_COLLECTION";
 
     private final Context mContext;
     private final AlarmManager mAlarmManager;
@@ -65,9 +72,12 @@
     private static final Object sStatsdLock = new Object();
 
     private final PendingIntent mAnomalyAlarmIntent;
-    private final PendingIntent mPollingAlarmIntent;
+    private final PendingIntent mPullingAlarmIntent;
     private final BroadcastReceiver mAppUpdateReceiver;
     private final BroadcastReceiver mUserUpdateReceiver;
+    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+    private final KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
 
     public StatsCompanionService(Context context) {
         super();
@@ -76,8 +86,8 @@
 
         mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
                 new Intent(mContext, AnomalyAlarmReceiver.class), 0);
-        mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
-                new Intent(mContext, PollingAlarmReceiver.class), 0);
+        mPullingAlarmIntent = PendingIntent.getBroadcast(
+            mContext, 0, new Intent(mContext, PullingAlarmReceiver.class), 0);
         mAppUpdateReceiver = new AppUpdateReceiver();
         mUserUpdateReceiver = new BroadcastReceiver() {
             @Override
@@ -100,6 +110,22 @@
             }
         };
         Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
+        PowerProfile powerProfile = new PowerProfile(context);
+        final int numClusters = powerProfile.getNumCpuClusters();
+        mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+        int firstCpuOfCluster = 0;
+        for (int i = 0; i < numClusters; i++) {
+            final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
+            mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+                            numSpeedSteps);
+            firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
+        }
+    }
+
+    @Override
+    public void sendBroadcast(String pkg, String cls) {
+        mContext.sendBroadcastAsUser(new Intent(ACTION_TRIGGER_COLLECTION).setClassName(pkg, cls),
+                UserHandle.SYSTEM);
     }
 
     private final static int[] toIntArray(List<Integer> list) {
@@ -144,18 +170,19 @@
     public final static class AppUpdateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
             /**
              * App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid
              * waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
+             * If we can't find the value for EXTRA_REPLACING, we default to false.
              */
-            if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
-                    intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+            if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED)
+                    && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                 return; // Keep only replacing or normal add and remove.
             }
+            Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
             synchronized (sStatsdLock) {
                 if (sStatsd == null) {
-                    Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
+                    Slog.w(TAG, "Could not access statsd to inform it of an app update");
                     return;
                 }
                 try {
@@ -205,24 +232,25 @@
         }
     }
 
-    public final static class PollingAlarmReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (DEBUG) Slog.d(TAG, "Time to poll something.");
-            synchronized (sStatsdLock) {
-                if (sStatsd == null) {
-                    Slog.w(TAG, "Could not access statsd to inform it of polling alarm firing");
-                    return;
-                }
-                try {
-                    // Two-way call to statsd to retain AlarmManager wakelock
-                    sStatsd.informPollAlarmFired();
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to inform statsd of polling alarm firing", e);
-                }
-            }
-            // AlarmManager releases its own wakelock here.
+    public final static class PullingAlarmReceiver extends BroadcastReceiver {
+      @Override
+      public void onReceive(Context context, Intent intent) {
+        if (DEBUG)
+          Slog.d(TAG, "Time to poll something.");
+        synchronized (sStatsdLock) {
+          if (sStatsd == null) {
+            Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing");
+            return;
+          }
+          try {
+            // Two-way call to statsd to retain AlarmManager wakelock
+            sStatsd.informPollAlarmFired();
+          } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to inform statsd of pulling alarm firing", e);
+          }
         }
+        // AlarmManager releases its own wakelock here.
+      }
     }
 
     @Override // Binder call
@@ -253,70 +281,212 @@
     }
 
     @Override // Binder call
-    public void setPollingAlarms(long timestampMs, long intervalMs) {
-        enforceCallingPermission();
-        if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs
-                + " every " + intervalMs + "ms");
-        final long callingToken = Binder.clearCallingIdentity();
-        try {
-            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
-            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
-            // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
-            mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs,
-                    mPollingAlarmIntent);
-        } finally {
-            Binder.restoreCallingIdentity(callingToken);
-        }
+    public void setPullingAlarms(long timestampMs, long intervalMs) {
+      enforceCallingPermission();
+      if (DEBUG)
+        Slog.d(TAG, "Setting pulling alarm for " + timestampMs + " every " + intervalMs + "ms");
+      final long callingToken = Binder.clearCallingIdentity();
+      try {
+        // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+        // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+        // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
+        mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs, mPullingAlarmIntent);
+      } finally {
+        Binder.restoreCallingIdentity(callingToken);
+      }
     }
 
     @Override // Binder call
-    public void cancelPollingAlarms() {
-        enforceCallingPermission();
-        if (DEBUG) Slog.d(TAG, "Cancelling polling alarm");
-        final long callingToken = Binder.clearCallingIdentity();
-        try {
-            mAlarmManager.cancel(mPollingAlarmIntent);
-        } finally {
-            Binder.restoreCallingIdentity(callingToken);
-        }
+    public void cancelPullingAlarms() {
+      enforceCallingPermission();
+      if (DEBUG)
+        Slog.d(TAG, "Cancelling pulling alarm");
+      final long callingToken = Binder.clearCallingIdentity();
+      try {
+        mAlarmManager.cancel(mPullingAlarmIntent);
+      } finally {
+        Binder.restoreCallingIdentity(callingToken);
+      }
     }
 
-    // These values must be kept in sync with cmd/statsd/StatsPullerManager.h.
-    // TODO: pull the constant from stats_events.proto instead
-    private static final int PULL_CODE_KERNEL_WAKELOCKS = 20;
-
-    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
-    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
-
-    @Override // Binder call
-    public StatsLogEventWrapper[] pullData(int pullCode) {
-        enforceCallingPermission();
-        if (DEBUG) {
-            Slog.d(TAG, "Pulling " + pullCode);
-        }
-
+    private StatsLogEventWrapper[] addNetworkStats(int tag, NetworkStats stats, boolean withFGBG) {
         List<StatsLogEventWrapper> ret = new ArrayList<>();
-        switch (pullCode) {
-            case PULL_CODE_KERNEL_WAKELOCKS: {
-                final KernelWakelockStats wakelockStats =
-                        mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
-                for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
-                    String name = ent.getKey();
-                    KernelWakelockStats.Entry kws = ent.getValue();
-                    StatsLogEventWrapper e = new StatsLogEventWrapper(101, 4);
-                    e.writeInt(kws.mCount);
-                    e.writeInt(kws.mVersion);
-                    e.writeLong(kws.mTotalTime);
-                    e.writeString(name);
-                    ret.add(e);
+        int size = stats.size();
+        NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+        for (int j = 0; j < size; j++) {
+            stats.getValues(j, entry);
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tag, withFGBG ? 6 : 5);
+            e.writeInt(entry.uid);
+            if (withFGBG) {
+                e.writeInt(entry.set);
+            }
+            e.writeLong(entry.rxBytes);
+            e.writeLong(entry.rxPackets);
+            e.writeLong(entry.txBytes);
+            e.writeLong(entry.txPackets);
+            ret.add(e);
+        }
+        return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+    }
+
+    /**
+     * Allows rollups per UID but keeping the set (foreground/background) slicing.
+     * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
+     */
+    private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
+        final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+        entry.iface = NetworkStats.IFACE_ALL;
+        entry.tag = NetworkStats.TAG_NONE;
+        entry.metered = NetworkStats.METERED_ALL;
+        entry.roaming = NetworkStats.ROAMING_ALL;
+
+        int size = stats.size();
+        NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
+        for (int i = 0; i < size; i++) {
+            stats.getValues(i, recycle);
+
+            // Skip specific tags, since already counted in TAG_NONE
+            if (recycle.tag != NetworkStats.TAG_NONE) continue;
+
+            entry.set = recycle.set; // Allows slicing by background/foreground
+            entry.uid = recycle.uid;
+            entry.rxBytes = recycle.rxBytes;
+            entry.rxPackets = recycle.rxPackets;
+            entry.txBytes = recycle.txBytes;
+            entry.txPackets = recycle.txPackets;
+            // Operations purposefully omitted since we don't use them for statsd.
+            ret.combineValues(entry);
+        }
+        return ret;
+    }
+
+    @Override // Binder call
+    public StatsLogEventWrapper[] pullData(int tagId) {
+        enforceCallingPermission();
+        if (DEBUG)
+            Slog.d(TAG, "Pulling " + tagId);
+
+        switch (tagId) {
+            case StatsLog.WIFI_BYTES_TRANSFERRED: {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    // TODO: Consider caching the following call to get BatteryStatsInternal.
+                    BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+                    String[] ifaces = bs.getWifiIfaces();
+                    if (ifaces.length == 0) {
+                        return null;
+                    }
+                    NetworkStatsFactory nsf = new NetworkStatsFactory();
+                    // Combine all the metrics per Uid into one record.
+                    NetworkStats stats = nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+                            NetworkStats.TAG_NONE, null).groupedByUid();
+                    return addNetworkStats(tagId, stats, false);
+                } catch (java.io.IOException e) {
+                    Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
                 }
                 break;
             }
+            case StatsLog.MOBILE_BYTES_TRANSFERRED: {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+                    String[] ifaces = bs.getMobileIfaces();
+                    if (ifaces.length == 0) {
+                        return null;
+                    }
+                    NetworkStatsFactory nsf = new NetworkStatsFactory();
+                    // Combine all the metrics per Uid into one record.
+                    NetworkStats stats = nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+                        NetworkStats.TAG_NONE, null).groupedByUid();
+                    return addNetworkStats(tagId, stats, false);
+                } catch (java.io.IOException e) {
+                    Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                break;
+            }
+            case StatsLog.WIFI_BYTES_TRANSFERRED_BY_FG_BG: {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+                    String[] ifaces = bs.getWifiIfaces();
+                    if (ifaces.length == 0) {
+                        return null;
+                    }
+                    NetworkStatsFactory nsf = new NetworkStatsFactory();
+                    NetworkStats stats = rollupNetworkStatsByFGBG(
+                            nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+                            NetworkStats.TAG_NONE, null));
+                    return addNetworkStats(tagId, stats, true);
+                } catch (java.io.IOException e) {
+                    Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                break;
+            }
+            case StatsLog.MOBILE_BYTES_TRANSFERRED_BY_FG_BG: {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+                    String[] ifaces = bs.getMobileIfaces();
+                    if (ifaces.length == 0) {
+                        return null;
+                    }
+                    NetworkStatsFactory nsf = new NetworkStatsFactory();
+                    NetworkStats stats = rollupNetworkStatsByFGBG(
+                            nsf.readNetworkStatsDetail(NetworkStats.UID_ALL, ifaces,
+                            NetworkStats.TAG_NONE, null));
+                    return addNetworkStats(tagId, stats, true);
+                } catch (java.io.IOException e) {
+                    Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                break;
+            }
+            case StatsLog.KERNEL_WAKELOCK_PULLED: {
+                final KernelWakelockStats wakelockStats =
+                        mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+                List<StatsLogEventWrapper> ret = new ArrayList();
+                for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+                    String name = ent.getKey();
+                    KernelWakelockStats.Entry kws = ent.getValue();
+                    StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 4);
+                    e.writeString(name);
+                    e.writeInt(kws.mCount);
+                    e.writeInt(kws.mVersion);
+                    e.writeLong(kws.mTotalTime);
+                    ret.add(e);
+                }
+                return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+            }
+            case StatsLog.CPU_TIME_PER_FREQ_PULLED: {
+                List<StatsLogEventWrapper> ret = new ArrayList();
+                for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+                    long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readDelta();
+                    if (clusterTimeMs != null) {
+                        for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
+                            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
+                            e.writeInt(tagId);
+                            e.writeInt(speed);
+                            e.writeLong(clusterTimeMs[speed]);
+                            ret.add(e);
+                        }
+                    }
+                }
+                return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+            }
             default:
-                Slog.w(TAG, "No such pollable data as " + pullCode);
+                Slog.w(TAG, "No such tagId data as " + tagId);
                 return null;
         }
-        return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+        return null;
     }
 
     @Override // Binder call
@@ -440,7 +610,7 @@
             mContext.unregisterReceiver(mAppUpdateReceiver);
             mContext.unregisterReceiver(mUserUpdateReceiver);
             cancelAnomalyAlarm();
-            cancelPollingAlarms();
+            cancelPullingAlarms();
         }
     }
 
diff --git a/com/android/server/updates/TzDataInstallReceiver.java b/com/android/server/updates/TzDataInstallReceiver.java
deleted file mode 100644
index cabce18..0000000
--- a/com/android/server/updates/TzDataInstallReceiver.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.updates;
-
-import com.android.timezone.distro.TimeZoneDistro;
-import com.android.timezone.distro.installer.TimeZoneDistroInstaller;
-
-import android.util.Slog;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * An install receiver responsible for installing timezone data updates.
- */
-public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver {
-
-    private static final String TAG = "TZDataInstallReceiver";
-
-    private static final File SYSTEM_TZ_DATA_FILE = new File("/system/usr/share/zoneinfo/tzdata");
-    private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
-    private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/";
-    private static final String UPDATE_METADATA_DIR_NAME = "metadata/";
-    private static final String UPDATE_VERSION_FILE_NAME = "version";
-    private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_distro.zip";
-
-    private final TimeZoneDistroInstaller installer;
-
-    public TzDataInstallReceiver() {
-        super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME,
-                UPDATE_VERSION_FILE_NAME);
-        installer = new TimeZoneDistroInstaller(TAG, SYSTEM_TZ_DATA_FILE, TZ_DATA_DIR);
-    }
-
-    @Override
-    protected void install(byte[] content, int version) throws IOException {
-        TimeZoneDistro distro = new TimeZoneDistro(content);
-        boolean valid = installer.install(distro);
-        Slog.i(TAG, "Timezone data install valid for this device: " + valid);
-        // Even if !valid, we call super.install(). Only in the event of an exception should we
-        // not. If we didn't do this we could attempt to install repeatedly.
-        super.install(content, version);
-    }
-}
diff --git a/com/android/server/usage/AppIdleHistory.java b/com/android/server/usage/AppIdleHistory.java
index e5d3915..c5ca330 100644
--- a/com/android/server/usage/AppIdleHistory.java
+++ b/com/android/server/usage/AppIdleHistory.java
@@ -103,7 +103,7 @@
         long lastUsedScreenTime;
         @StandbyBuckets int currentBucket;
         String bucketingReason;
-        int lastInformedState;
+        int lastInformedBucket;
     }
 
     AppIdleHistory(File storageDir, long elapsedRealtime) {
@@ -333,13 +333,12 @@
     }
 
     boolean shouldInformListeners(String packageName, int userId,
-            long elapsedRealtime, boolean isIdle) {
+            long elapsedRealtime, int bucket) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
-        int targetState = isIdle? STATE_IDLE : STATE_ACTIVE;
-        if (appUsageHistory.lastInformedState != (isIdle ? STATE_IDLE : STATE_ACTIVE)) {
-            appUsageHistory.lastInformedState = targetState;
+        if (appUsageHistory.lastInformedBucket != bucket) {
+            appUsageHistory.lastInformedBucket = bucket;
             return true;
         }
         return false;
diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java
index 17fde57..5623a68 100644
--- a/com/android/server/usage/AppStandbyController.java
+++ b/com/android/server/usage/AppStandbyController.java
@@ -160,7 +160,6 @@
     private final Context mContext;
 
     // TODO: Provide a mechanism to set an external bucketing service
-    private boolean mUseInternalBucketingHeuristics = true;
 
     private AppWidgetManager mAppWidgetManager;
     private PowerManager mPowerManager;
@@ -367,29 +366,33 @@
                     Slog.d(TAG, "   Checking idle state for " + packageName);
                 }
                 if (isSpecial) {
-                    maybeInformListeners(packageName, userId, elapsedRealtime, false);
-                } else if (mUseInternalBucketingHeuristics) {
+                    maybeInformListeners(packageName, userId, elapsedRealtime,
+                            AppStandby.STANDBY_BUCKET_ACTIVE);
+                } else {
                     synchronized (mAppIdleLock) {
-                        int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
-                                elapsedRealtime);
                         String bucketingReason = mAppIdleHistory.getAppStandbyReason(packageName,
                                 userId, elapsedRealtime);
-                        if (bucketingReason != null
-                                && (bucketingReason.equals(AppStandby.REASON_FORCED)
-                                    || bucketingReason.startsWith(AppStandby.REASON_PREDICTED))) {
+                        // If the bucket was forced by the developer, leave it alone
+                        if (AppStandby.REASON_FORCED.equals(bucketingReason)) {
                             continue;
                         }
-                        int newBucket = getBucketForLocked(packageName, userId,
-                                            elapsedRealtime);
-                        if (DEBUG) {
-                            Slog.d(TAG, "     Old bucket=" + oldBucket
-                                    + ", newBucket=" + newBucket);
-                        }
-                        if (oldBucket != newBucket) {
-                            mAppIdleHistory.setAppStandbyBucket(packageName, userId,
-                                    elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT);
-                            maybeInformListeners(packageName, userId, elapsedRealtime,
-                                    newBucket >= AppStandby.STANDBY_BUCKET_RARE);
+                        // If the bucket was moved up due to usage, let the timeouts apply.
+                        if (AppStandby.REASON_USAGE.equals(bucketingReason)
+                                || AppStandby.REASON_TIMEOUT.equals(bucketingReason)) {
+                            int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
+                                    elapsedRealtime);
+                            int newBucket = getBucketForLocked(packageName, userId,
+                                    elapsedRealtime);
+                            if (DEBUG) {
+                                Slog.d(TAG, "     Old bucket=" + oldBucket
+                                        + ", newBucket=" + newBucket);
+                            }
+                            if (oldBucket < newBucket) {
+                                mAppIdleHistory.setAppStandbyBucket(packageName, userId,
+                                        elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT);
+                                maybeInformListeners(packageName, userId, elapsedRealtime,
+                                        newBucket);
+                            }
                         }
                     }
                 }
@@ -403,12 +406,12 @@
     }
 
     private void maybeInformListeners(String packageName, int userId,
-            long elapsedRealtime, boolean isIdle) {
+            long elapsedRealtime, int bucket) {
         synchronized (mAppIdleLock) {
             if (mAppIdleHistory.shouldInformListeners(packageName, userId,
-                    elapsedRealtime, isIdle)) {
+                    elapsedRealtime, bucket)) {
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                        userId, isIdle ? 1 : 0, packageName));
+                        userId, bucket, packageName));
             }
         }
     }
@@ -461,11 +464,13 @@
         if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
         boolean paroled = false;
         synchronized (mAppIdleLock) {
-            final long timeSinceLastParole = mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
+            final long timeSinceLastParole =
+                    mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
             if (!deviceIdle
                     && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
                 if (DEBUG) {
-                    Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
+                    Slog.i(TAG,
+                            "Bringing idle apps out of inactive state due to deviceIdleMode=false");
                 }
                 paroled = true;
             } else if (deviceIdle) {
@@ -491,7 +496,8 @@
                     || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
                 mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
                 if (previouslyIdle) {
-                    maybeInformListeners(event.mPackage, userId, elapsedRealtime, false);
+                    maybeInformListeners(event.mPackage, userId, elapsedRealtime,
+                            AppStandby.STANDBY_BUCKET_ACTIVE);
                     notifyBatteryStats(event.mPackage, userId, false);
                 }
             }
@@ -729,7 +735,8 @@
 
     void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
             String reason, long elapsedRealtime) {
-        mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason);
+        mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
+                reason);
     }
 
     private boolean isActiveDeviceAdmin(String packageName, int userId) {
@@ -786,9 +793,10 @@
         return packageName != null && packageName.equals(activeScorer);
     }
 
-    void informListeners(String packageName, int userId, boolean isIdle) {
+    void informListeners(String packageName, int userId, int bucket) {
+        final boolean idle = bucket >= AppStandby.STANDBY_BUCKET_RARE;
         for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
-            listener.onAppIdleStateChanged(packageName, userId, isIdle);
+            listener.onAppIdleStateChanged(packageName, userId, idle, bucket);
         }
     }
 
@@ -1037,7 +1045,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_INFORM_LISTENERS:
-                    informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
+                    informListeners((String) msg.obj, msg.arg1, msg.arg2);
                     break;
 
                 case MSG_FORCE_IDLE_STATE:
@@ -1187,7 +1195,8 @@
                 mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
                         SCREEN_TIME_THRESHOLDS);
 
-                String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null);
+                String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS,
+                        null);
                 mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
                         ELAPSED_TIME_THRESHOLDS);
             }
diff --git a/com/android/server/usb/UsbDeviceManager.java b/com/android/server/usb/UsbDeviceManager.java
index e65d360..1b057f9 100644
--- a/com/android/server/usb/UsbDeviceManager.java
+++ b/com/android/server/usb/UsbDeviceManager.java
@@ -893,7 +893,7 @@
                         updateCurrentAccessory();
                     }
                     if (mBootCompleted) {
-                        if (!mConnected) {
+                        if (!mConnected && !hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT)) {
                             // restore defaults when USB is disconnected
                             setEnabledFunctions(null, !mAdbEnabled, false);
                         }
diff --git a/com/android/server/usb/UsbProfileGroupSettingsManager.java b/com/android/server/usb/UsbProfileGroupSettingsManager.java
index ebb5a62..917e651 100644
--- a/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -32,9 +32,10 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.XmlResourceParser;
+import android.hardware.usb.AccessoryFilter;
+import android.hardware.usb.DeviceFilter;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbManager;
 import android.os.AsyncTask;
 import android.os.Environment;
@@ -58,7 +59,6 @@
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -71,7 +71,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 
 class UsbProfileGroupSettingsManager {
     private static final String TAG = UsbProfileGroupSettingsManager.class.getSimpleName();
@@ -157,404 +156,6 @@
         }
     }
 
-    // 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.
-    private static class DeviceFilter {
-        // 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 +
-                    "]";
-        }
-    }
-
-    // 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.
-    private static 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 + "\"]";
-        }
-    }
-
     private class MyPackageMonitor extends PackageMonitor {
         @Override
         public void onPackageAdded(String packageName, int uid) {
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 1569ac3..44e5314 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.ShortcutServiceInternal;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
@@ -44,6 +45,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
@@ -53,6 +55,7 @@
 import android.service.voice.VoiceInteractionSession;
 import android.speech.RecognitionService;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
@@ -63,6 +66,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
@@ -84,7 +88,9 @@
     final ContentResolver mResolver;
     final DatabaseHelper mDbHelper;
     final ActivityManagerInternal mAmInternal;
-    final TreeSet<Integer> mLoadedKeyphraseIds;
+    final UserManager mUserManager;
+    final ArraySet<Integer> mLoadedKeyphraseIds = new ArraySet<>();
+    ShortcutServiceInternal mShortcutServiceInternal;
     SoundTriggerInternal mSoundTriggerInternal;
 
     private final RemoteCallbackList<IVoiceInteractionSessionListener>
@@ -96,8 +102,10 @@
         mResolver = context.getContentResolver();
         mDbHelper = new DatabaseHelper(context);
         mServiceStub = new VoiceInteractionManagerServiceStub();
-        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
-        mLoadedKeyphraseIds = new TreeSet<Integer>();
+        mAmInternal = Preconditions.checkNotNull(
+                LocalServices.getService(ActivityManagerInternal.class));
+        mUserManager = Preconditions.checkNotNull(
+                context.getSystemService(UserManager.class));
 
         PackageManagerInternal packageManagerInternal = LocalServices.getService(
                 PackageManagerInternal.class);
@@ -124,6 +132,8 @@
     @Override
     public void onBootPhase(int phase) {
         if (PHASE_SYSTEM_SERVICES_READY == phase) {
+            mShortcutServiceInternal = Preconditions.checkNotNull(
+                    LocalServices.getService(ShortcutServiceInternal.class));
             mSoundTriggerInternal = LocalServices.getService(SoundTriggerInternal.class);
         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
             mServiceStub.systemRunning(isSafeMode());
@@ -180,6 +190,7 @@
 
         private boolean mSafeMode;
         private int mCurUser;
+        private boolean mCurUserUnlocked;
         private final boolean mEnableService;
 
         VoiceInteractionManagerServiceStub() {
@@ -381,6 +392,7 @@
         public void switchUser(int userHandle) {
             synchronized (this) {
                 mCurUser = userHandle;
+                mCurUserUnlocked = false;
                 switchImplementationIfNeededLocked(false);
             }
         }
@@ -409,13 +421,24 @@
                     }
                 }
 
+                final boolean hasComponent = serviceComponent != null && serviceInfo != null;
+
+                if (mUserManager.isUserUnlockingOrUnlocked(mCurUser)) {
+                    if (hasComponent) {
+                        mShortcutServiceInternal.setShortcutHostPackage(TAG,
+                                serviceComponent.getPackageName(), mCurUser);
+                    } else {
+                        mShortcutServiceInternal.setShortcutHostPackage(TAG, null, mCurUser);
+                    }
+                }
+
                 if (force || mImpl == null || mImpl.mUser != mCurUser
                         || !mImpl.mComponent.equals(serviceComponent)) {
                     unloadAllKeyphraseModels();
                     if (mImpl != null) {
                         mImpl.shutdownLocked();
                     }
-                    if (serviceComponent != null && serviceInfo != null) {
+                    if (hasComponent) {
                         mImpl = new VoiceInteractionManagerServiceImpl(mContext,
                                 UiThread.getHandler(), this, mCurUser, serviceComponent);
                         mImpl.startLocked();
@@ -953,12 +976,14 @@
         }
 
         private synchronized void unloadAllKeyphraseModels() {
-            for (int keyphraseId : mLoadedKeyphraseIds) {
+            for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    int status = mSoundTriggerInternal.unloadKeyphraseModel(keyphraseId);
+                    int status = mSoundTriggerInternal.unloadKeyphraseModel(
+                            mLoadedKeyphraseIds.valueAt(i));
                     if (status != SoundTriggerInternal.STATUS_OK) {
-                        Slog.w(TAG, "Failed to unload keyphrase " + keyphraseId + ":" + status);
+                        Slog.w(TAG, "Failed to unload keyphrase " + mLoadedKeyphraseIds.valueAt(i)
+                                + ":" + status);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(caller);
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b040a63..7541b92 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -55,6 +55,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 
 class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
@@ -162,13 +163,16 @@
                     mInfo.getServiceInfo().applicationInfo.uid, mHandler);
         }
         List<IBinder> activityTokens = null;
-        if (activityToken == null) {
+        if (activityToken != null) {
+            activityTokens = new ArrayList<>();
+            activityTokens.add(activityToken);
+        } else {
             // Let's get top activities from all visible stacks
             activityTokens = LocalServices.getService(ActivityManagerInternal.class)
                     .getTopVisibleActivities();
         }
         return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
-                activityToken, activityTokens);
+                activityTokens);
     }
 
     public boolean hideSessionLocked() {
diff --git a/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index d394d63..e0d9c73 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -16,6 +16,16 @@
 
 package com.android.server.voiceinteraction;
 
+import static android.app.ActivityManagerInternal.ASSIST_KEY_CONTENT;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
@@ -43,31 +53,24 @@
 import android.service.voice.VoiceInteractionSession;
 import android.util.Slog;
 import android.view.IWindowManager;
-import android.view.WindowManager;
 
 import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IAssistScreenshotReceiver;
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.os.IResultReceiver;
 import com.android.server.LocalServices;
+import com.android.server.am.AssistDataRequester;
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-
-final class VoiceInteractionSessionConnection implements ServiceConnection {
+final class VoiceInteractionSessionConnection implements ServiceConnection,
+        AssistDataRequesterCallbacks {
 
     final static String TAG = "VoiceInteractionServiceManager";
 
-    private static final String KEY_RECEIVER_EXTRA_COUNT = "count";
-    private static final String KEY_RECEIVER_EXTRA_INDEX = "index";
-
     final IBinder mToken = new Binder();
     final Object mLock;
     final ComponentName mSessionComponentName;
@@ -90,27 +93,8 @@
     IVoiceInteractionSessionService mService;
     IVoiceInteractionSession mSession;
     IVoiceInteractor mInteractor;
-    boolean mHaveAssistData;
-    int mPendingAssistDataCount;
-    ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>();
-    boolean mHaveScreenshot;
-    Bitmap mScreenshot;
     ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
-
-    static class AssistDataForActivity {
-        int activityIndex;
-        int activityCount;
-        Bundle data;
-
-        public AssistDataForActivity(Bundle data) {
-            this.data = data;
-            Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
-            if (receiverExtras != null) {
-                activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
-                activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
-            }
-        }
-    }
+    AssistDataRequester mAssistDataRequester;
 
     IVoiceInteractionSessionShowCallback mShowCallback =
             new IVoiceInteractionSessionShowCallback.Stub() {
@@ -146,32 +130,6 @@
         }
     };
 
-    final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
-        @Override
-        public void send(int resultCode, Bundle resultData) throws RemoteException {
-            synchronized (mLock) {
-                if (mShown) {
-                    mHaveAssistData = true;
-                    mAssistData.add(new AssistDataForActivity(resultData));
-                    deliverSessionDataLocked();
-                }
-            }
-        }
-    };
-
-    final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
-        @Override
-        public void send(Bitmap screenshot) throws RemoteException {
-            synchronized (mLock) {
-                if (mShown) {
-                    mHaveScreenshot = true;
-                    mScreenshot = screenshot;
-                    deliverSessionDataLocked();
-                }
-            }
-        }
-    };
-
     public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
             Context context, Callback callback, int callingUid, Handler handler) {
         mLock = lock;
@@ -185,6 +143,9 @@
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         mAppOps = context.getSystemService(AppOpsManager.class);
+        mAssistDataRequester = new AssistDataRequester(mContext, mAm, mIWindowManager,
+                (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
+                this, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
         IBinder permOwner = null;
         try {
             permOwner = mAm.newUriPermissionOwner("voicesession:"
@@ -224,8 +185,7 @@
     }
 
     public boolean showLocked(Bundle args, int flags, int disabledContext,
-            IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken,
-            List<IBinder> topActivities) {
+            IVoiceInteractionSessionShowCallback showCallback, List<IBinder> topActivities) {
         if (mBound) {
             if (!mFullyBound) {
                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -233,75 +193,21 @@
                                 | Context.BIND_FOREGROUND_SERVICE,
                         new UserHandle(mUser));
             }
+
             mShown = true;
-            boolean isAssistDataAllowed = true;
-            try {
-                isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity();
-            } catch (RemoteException e) {
-            }
-            disabledContext |= getUserDisabledShowContextLocked();
-            boolean structureEnabled = isAssistDataAllowed
-                    && (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0;
-            boolean screenshotEnabled = isAssistDataAllowed && structureEnabled
-                    && (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0;
             mShowArgs = args;
             mShowFlags = flags;
-            mHaveAssistData = false;
-            mPendingAssistDataCount = 0;
-            boolean needDisclosure = false;
-            if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
-                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
-                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
-                        && structureEnabled) {
-                    mAssistData.clear();
-                    final int count = activityToken != null ? 1 : topActivities.size();
-                    for (int i = 0; i < count; i++) {
-                        IBinder topActivity = count == 1 ? activityToken : topActivities.get(i);
-                        try {
-                            MetricsLogger.count(mContext, "assist_with_context", 1);
-                            Bundle receiverExtras = new Bundle();
-                            receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
-                            receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, count);
-                            if (mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
-                                    mAssistReceiver, receiverExtras, topActivity,
-                                    /* focused= */ i == 0, /* newSessionId= */ i == 0)) {
-                                needDisclosure = true;
-                                mPendingAssistDataCount++;
-                            } else if (i == 0) {
-                                // Wasn't allowed... given that, let's not do the screenshot either.
-                                mHaveAssistData = true;
-                                mAssistData.clear();
-                                screenshotEnabled = false;
-                                break;
-                            }
-                        } catch (RemoteException e) {
-                        }
-                    }
-                } else {
-                    mHaveAssistData = true;
-                    mAssistData.clear();
-                }
-            } else {
-                mAssistData.clear();
-            }
-            mHaveScreenshot = false;
-            if ((flags&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0) {
-                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
-                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
-                        && screenshotEnabled) {
-                    try {
-                        MetricsLogger.count(mContext, "assist_with_screen", 1);
-                        needDisclosure = true;
-                        mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
-                    } catch (RemoteException e) {
-                    }
-                } else {
-                    mHaveScreenshot = true;
-                    mScreenshot = null;
-                }
-            } else {
-                mScreenshot = null;
-            }
+
+            disabledContext |= getUserDisabledShowContextLocked();
+            mAssistDataRequester.requestAssistData(topActivities,
+                    (flags & VoiceInteractionSession.SHOW_WITH_ASSIST) != 0,
+                    (flags & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0,
+                    (disabledContext & VoiceInteractionSession.SHOW_WITH_ASSIST) == 0,
+                    (disabledContext & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0,
+                    mCallingUid, mSessionComponentName.getPackageName());
+
+            boolean needDisclosure = mAssistDataRequester.getPendingDataCount() > 0
+                    || mAssistDataRequester.getPendingScreenshotCount() > 0;
             if (needDisclosure && AssistUtils.shouldDisclose(mContext, mSessionComponentName)) {
                 mHandler.post(mShowAssistDisclosureRunnable);
             }
@@ -312,7 +218,7 @@
                     mShowFlags = 0;
                 } catch (RemoteException e) {
                 }
-                deliverSessionDataLocked();
+                mAssistDataRequester.processPendingAssistData();
             } else if (showCallback != null) {
                 mPendingShowCallbacks.add(showCallback);
             }
@@ -328,6 +234,67 @@
         return false;
     }
 
+    @Override
+    public boolean canHandleReceivedAssistDataLocked() {
+        return mSession != null;
+    }
+
+    @Override
+    public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+        // Return early if we have no session
+        if (mSession == null) {
+            return;
+        }
+
+        if (data == null) {
+            try {
+                mSession.handleAssist(null, null, null, 0, 0);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        } else {
+            final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
+            final AssistStructure structure = data.getParcelable(ASSIST_KEY_STRUCTURE);
+            final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
+            final int uid = data.getInt(Intent.EXTRA_ASSIST_UID, -1);
+            if (uid >= 0 && content != null) {
+                Intent intent = content.getIntent();
+                if (intent != null) {
+                    ClipData clipData = intent.getClipData();
+                    if (clipData != null && Intent.isAccessUriMode(intent.getFlags())) {
+                        grantClipDataPermissions(clipData, intent.getFlags(), uid,
+                                mCallingUid, mSessionComponentName.getPackageName());
+                    }
+                }
+                ClipData clipData = content.getClipData();
+                if (clipData != null) {
+                    grantClipDataPermissions(clipData, FLAG_GRANT_READ_URI_PERMISSION,
+                            uid, mCallingUid, mSessionComponentName.getPackageName());
+                }
+            }
+            try {
+                mSession.handleAssist(assistData, structure, content, activityIndex,
+                        activityCount);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        }
+    }
+
+    @Override
+    public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+        // Return early if we have no session
+        if (mSession == null) {
+            return;
+        }
+
+        try {
+            mSession.handleScreenshot(screenshot);
+        } catch (RemoteException e) {
+            // Ignore
+        }
+    }
+
     void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
         if (!"content".equals(uri.getScheme())) {
             return;
@@ -341,7 +308,7 @@
             int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
             uri = ContentProvider.getUriWithoutUserId(uri);
             mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
-                    uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
+                    uri, FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
         } catch (RemoteException e) {
         } catch (SecurityException e) {
             Slog.w(TAG, "Can't propagate permission", e);
@@ -370,89 +337,13 @@
         }
     }
 
-    void deliverSessionDataLocked() {
-        if (mSession == null) {
-            return;
-        }
-        if (mHaveAssistData) {
-            AssistDataForActivity assistData;
-            if (mAssistData.isEmpty()) {
-                // We're not actually going to get any data, deliver some nothing
-                try {
-                    mSession.handleAssist(null, null, null, 0, 0);
-                } catch (RemoteException e) {
-                }
-            } else {
-                while (!mAssistData.isEmpty()) {
-                    if (mPendingAssistDataCount <= 0) {
-                        Slog.e(TAG, "mPendingAssistDataCount is " + mPendingAssistDataCount);
-                    }
-                    mPendingAssistDataCount--;
-                    assistData = mAssistData.remove(0);
-                    if (assistData.data == null) {
-                        try {
-                            mSession.handleAssist(null, null, null, assistData.activityIndex,
-                                    assistData.activityCount);
-                        } catch (RemoteException e) {
-                        }
-                    } else {
-                        deliverSessionDataLocked(assistData);
-                    }
-                }
-            }
-            if (mPendingAssistDataCount <= 0) {
-                mHaveAssistData = false;
-            } // else, more to come
-        }
-        if (mHaveScreenshot) {
-            try {
-                mSession.handleScreenshot(mScreenshot);
-            } catch (RemoteException e) {
-            }
-            mScreenshot = null;
-            mHaveScreenshot = false;
-        }
-    }
-
-    private void deliverSessionDataLocked(AssistDataForActivity assistDataForActivity) {
-        Bundle assistData = assistDataForActivity.data.getBundle(
-                VoiceInteractionSession.KEY_DATA);
-        AssistStructure structure = assistDataForActivity.data.getParcelable(
-                VoiceInteractionSession.KEY_STRUCTURE);
-        AssistContent content = assistDataForActivity.data.getParcelable(
-                VoiceInteractionSession.KEY_CONTENT);
-        int uid = assistDataForActivity.data.getInt(Intent.EXTRA_ASSIST_UID, -1);
-        if (uid >= 0 && content != null) {
-            Intent intent = content.getIntent();
-            if (intent != null) {
-                ClipData data = intent.getClipData();
-                if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
-                    grantClipDataPermissions(data, intent.getFlags(), uid,
-                            mCallingUid, mSessionComponentName.getPackageName());
-                }
-            }
-            ClipData data = content.getClipData();
-            if (data != null) {
-                grantClipDataPermissions(data,
-                        Intent.FLAG_GRANT_READ_URI_PERMISSION,
-                        uid, mCallingUid, mSessionComponentName.getPackageName());
-            }
-        }
-        try {
-            mSession.handleAssist(assistData, structure, content,
-                    assistDataForActivity.activityIndex, assistDataForActivity.activityCount);
-        } catch (RemoteException e) {
-        }
-    }
-
     public boolean hideLocked() {
         if (mBound) {
             if (mShown) {
                 mShown = false;
                 mShowArgs = null;
                 mShowFlags = 0;
-                mHaveAssistData = false;
-                mAssistData.clear();
+                mAssistDataRequester.cancel();
                 mPendingShowCallbacks.clear();
                 if (mSession != null) {
                     try {
@@ -462,8 +353,7 @@
                 }
                 try {
                     mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
-                            Intent.FLAG_GRANT_READ_URI_PERMISSION
-                                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                            FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION,
                             mUser);
                 } catch (RemoteException e) {
                 }
@@ -529,7 +419,7 @@
                 mShowFlags = 0;
             } catch (RemoteException e) {
             }
-            deliverSessionDataLocked();
+            mAssistDataRequester.processPendingAssistData();
         }
         return true;
     }
@@ -587,10 +477,7 @@
             pw.print(prefix); pw.print("mSession="); pw.println(mSession);
             pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
         }
-        pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
-        if (mHaveAssistData) {
-            pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
-        }
+        mAssistDataRequester.dump(prefix, pw);
     }
 
     private Runnable mShowAssistDisclosureRunnable = new Runnable() {
diff --git a/com/android/server/vr/VrManagerService.java b/com/android/server/vr/VrManagerService.java
index e7e4efc..5493207 100644
--- a/com/android/server/vr/VrManagerService.java
+++ b/com/android/server/vr/VrManagerService.java
@@ -166,6 +166,8 @@
     private boolean mUserUnlocked;
     private Vr2dDisplay mVr2dDisplay;
     private boolean mBootsToVr;
+    private boolean mStandby;
+    private boolean mUseStandbyToExitVrMode;
 
     // Handles events from the managed services (e.g. VrListenerService and any bound VR compositor
     // service).
@@ -203,7 +205,10 @@
      *
      */
     private void updateVrModeAllowedLocked() {
-        boolean allowed = mSystemSleepFlags == FLAG_ALL && mUserUnlocked;
+        boolean ignoreSleepFlags = mBootsToVr && mUseStandbyToExitVrMode;
+        boolean disallowedByStandby = mStandby && mUseStandbyToExitVrMode;
+        boolean allowed = (mSystemSleepFlags == FLAG_ALL || ignoreSleepFlags) && mUserUnlocked
+                && !disallowedByStandby;
         if (mVrModeAllowed != allowed) {
             mVrModeAllowed = allowed;
             if (DBG) Slog.d(TAG, "VR mode is " + ((allowed) ? "allowed" : "disallowed"));
@@ -273,6 +278,17 @@
         }
     }
 
+    private void setStandbyEnabled(boolean standby) {
+        synchronized(mLock) {
+            if (!mBootsToVr) {
+                Slog.e(TAG, "Attempting to set standby mode on a non-standalone device");
+                return;
+            }
+            mStandby = standby;
+            updateVrModeAllowedLocked();
+        }
+    }
+
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -587,6 +603,12 @@
         }
 
         @Override
+        public void setStandbyEnabled(boolean standby) {
+            enforceCallerPermissionAnyOf(Manifest.permission.ACCESS_VR_MANAGER);
+            VrManagerService.this.setStandbyEnabled(standby);
+        }
+
+        @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
@@ -733,6 +755,8 @@
         }
 
         mBootsToVr = SystemProperties.getBoolean("ro.boot.vr", false);
+        mUseStandbyToExitVrMode = mBootsToVr
+                && SystemProperties.getBoolean("persist.vr.use_standby_to_exit_vr_mode", true);
         publishLocalService(VrManagerInternal.class, new LocalService());
         publishBinderService(Context.VR_SERVICE, mVrManager.asBinder());
     }
diff --git a/com/android/server/wifi/HalDeviceManager.java b/com/android/server/wifi/HalDeviceManager.java
index 7a1e8d9..0cc735a 100644
--- a/com/android/server/wifi/HalDeviceManager.java
+++ b/com/android/server/wifi/HalDeviceManager.java
@@ -35,9 +35,9 @@
 import android.os.Handler;
 import android.os.HwRemoteBinder;
 import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.LongSparseArray;
 import android.util.MutableBoolean;
 import android.util.MutableInt;
 import android.util.SparseArray;
@@ -70,8 +70,12 @@
     @VisibleForTesting
     public static final String HAL_INSTANCE_NAME = "default";
 
+    private final Clock mClock;
+
     // public API
-    public HalDeviceManager() {
+    public HalDeviceManager(Clock clock) {
+        mClock = clock;
+
         mInterfaceAvailableForRequestListeners.put(IfaceType.STA, new HashSet<>());
         mInterfaceAvailableForRequestListeners.put(IfaceType.AP, new HashSet<>());
         mInterfaceAvailableForRequestListeners.put(IfaceType.P2P, new HashSet<>());
@@ -98,12 +102,13 @@
      * single copy kept.
      *
      * @param listener ManagerStatusListener listener object.
-     * @param looper Looper on which to dispatch listener. Null implies current looper.
+     * @param handler Handler on which to dispatch listener. Null implies a new Handler based on
+     *                the current looper.
      */
-    public void registerStatusListener(ManagerStatusListener listener, Looper looper) {
+    public void registerStatusListener(ManagerStatusListener listener, Handler handler) {
         synchronized (mLock) {
             if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener,
-                    looper == null ? Looper.myLooper() : looper))) {
+                    handler == null ? new Handler(Looper.myLooper()) : handler))) {
                 Log.w(TAG, "registerStatusListener: duplicate registration ignored");
             }
         }
@@ -192,37 +197,37 @@
      * @param destroyedListener Optional (nullable) listener to call when the allocated interface
      *                          is removed. Will only be registered and used if an interface is
      *                          created successfully.
-     * @param looper The looper on which to dispatch the listener. A null value indicates the
-     *               current thread.
+     * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
+     *                based on the current looper.
      * @return A newly created interface - or null if the interface could not be created.
      */
     public IWifiStaIface createStaIface(InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
-        return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, looper);
+            Handler handler) {
+        return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, handler);
     }
 
     /**
      * Create AP interface if possible (see createStaIface doc).
      */
     public IWifiApIface createApIface(InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
-        return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, looper);
+            Handler handler) {
+        return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, handler);
     }
 
     /**
      * Create P2P interface if possible (see createStaIface doc).
      */
     public IWifiP2pIface createP2pIface(InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
-        return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, looper);
+            Handler handler) {
+        return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, handler);
     }
 
     /**
      * Create NAN interface if possible (see createStaIface doc).
      */
     public IWifiNanIface createNanIface(InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
-        return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, looper);
+            Handler handler) {
+        return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, handler);
     }
 
     /**
@@ -263,11 +268,11 @@
      * and false on failure. This listener is in addition to the one registered when the interface
      * was created - allowing non-creators to monitor interface status.
      *
-     * Listener called-back on the specified looper - or on the current looper if a null is passed.
+     * Listener called-back on the specified handler - or on the current looper if a null is passed.
      */
     public boolean registerDestroyedListener(IWifiIface iface,
             InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
+            Handler handler) {
         String name = getName(iface);
         if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
 
@@ -279,8 +284,7 @@
             }
 
             return cacheEntry.destroyedListeners.add(
-                    new InterfaceDestroyedListenerProxy(destroyedListener,
-                            looper == null ? Looper.myLooper() : looper));
+                    new InterfaceDestroyedListenerProxy(destroyedListener, handler));
         }
     }
 
@@ -299,17 +303,16 @@
      * @param ifaceType The interface type (IfaceType) to be monitored.
      * @param listener Listener to call when an interface of the requested
      *                 type could be created
-     * @param looper The looper on which to dispatch the listener. A null value indicates the
-     *               current thread.
+     * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
+     *                on the current looper.
      */
     public void registerInterfaceAvailableForRequestListener(int ifaceType,
-            InterfaceAvailableForRequestListener listener, Looper looper) {
+            InterfaceAvailableForRequestListener listener, Handler handler) {
         if (DBG) Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
 
         synchronized (mLock) {
             mInterfaceAvailableForRequestListeners.get(ifaceType).add(
-                    new InterfaceAvailableForRequestListenerProxy(listener,
-                            looper == null ? Looper.myLooper() : looper));
+                    new InterfaceAvailableForRequestListenerProxy(listener, handler));
         }
 
         WifiChipInfo[] chipInfos = getAllChipInfo();
@@ -474,12 +477,14 @@
         public String name;
         public int type;
         public Set<InterfaceDestroyedListenerProxy> destroyedListeners = new HashSet<>();
+        public long creationTime;
 
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("{name=").append(name).append(", type=").append(type)
                     .append(", destroyedListeners.size()=").append(destroyedListeners.size())
+                    .append(", creationTime=").append(creationTime)
                     .append("}");
             return sb.toString();
         }
@@ -1208,8 +1213,8 @@
     private class ManagerStatusListenerProxy  extends
             ListenerProxy<ManagerStatusListener> {
         ManagerStatusListenerProxy(ManagerStatusListener statusListener,
-                Looper looper) {
-            super(statusListener, looper, "ManagerStatusListenerProxy");
+                Handler handler) {
+            super(statusListener, handler, true, "ManagerStatusListenerProxy");
         }
 
         @Override
@@ -1270,7 +1275,7 @@
     }
 
     private IWifiIface createIface(int ifaceType, InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
+            Handler handler) {
         if (DBG) Log.d(TAG, "createIface: ifaceType=" + ifaceType);
 
         synchronized (mLock) {
@@ -1288,7 +1293,7 @@
             }
 
             IWifiIface iface = createIfaceIfPossible(chipInfos, ifaceType, destroyedListener,
-                    looper);
+                    handler);
             if (iface != null) { // means that some configuration has changed
                 if (!dispatchAvailableForRequestListeners()) {
                     return null; // catastrophic failure - shut down
@@ -1300,7 +1305,7 @@
     }
 
     private IWifiIface createIfaceIfPossible(WifiChipInfo[] chipInfos, int ifaceType,
-            InterfaceDestroyedListener destroyedListener, Looper looper) {
+            InterfaceDestroyedListener destroyedListener, Handler handler) {
         if (DBG) {
             Log.d(TAG, "createIfaceIfPossible: chipInfos=" + Arrays.deepToString(chipInfos)
                     + ", ifaceType=" + ifaceType);
@@ -1341,9 +1346,9 @@
                     cacheEntry.type = ifaceType;
                     if (destroyedListener != null) {
                         cacheEntry.destroyedListeners.add(
-                                new InterfaceDestroyedListenerProxy(destroyedListener,
-                                        looper == null ? Looper.myLooper() : looper));
+                                new InterfaceDestroyedListenerProxy(destroyedListener, handler));
                     }
+                    cacheEntry.creationTime = mClock.getUptimeSinceBootMillis();
 
                     if (DBG) Log.d(TAG, "createIfaceIfPossible: added cacheEntry=" + cacheEntry);
                     mInterfaceInfoCache.put(cacheEntry.name, cacheEntry);
@@ -1468,7 +1473,8 @@
         if (isChipModeChangeProposed) {
             for (int type: IFACE_TYPES_BY_PRIORITY) {
                 if (chipInfo.ifaces[type].length != 0) {
-                    if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
+                    if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType,
+                            chipInfo.ifaces[ifaceType].length != 0)) {
                         if (DBG) {
                             Log.d(TAG, "Couldn't delete existing type " + type
                                     + " interfaces for requested type");
@@ -1498,17 +1504,17 @@
             }
 
             if (tooManyInterfaces > 0) { // may need to delete some
-                if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
+                if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType,
+                        chipInfo.ifaces[ifaceType].length != 0)) {
                     if (DBG) {
                         Log.d(TAG, "Would need to delete some higher priority interfaces");
                     }
                     return null;
                 }
 
-                // arbitrarily pick the first interfaces to delete
-                for (int i = 0; i < tooManyInterfaces; ++i) {
-                    interfacesToBeRemovedFirst.add(chipInfo.ifaces[type][i]);
-                }
+                // delete the most recently created interfaces
+                interfacesToBeRemovedFirst = selectInterfacesToDelete(tooManyInterfaces,
+                        chipInfo.ifaces[type]);
             }
         }
 
@@ -1576,14 +1582,20 @@
      * interface type.
      *
      * Rules:
-     * 1. Request for AP or STA will destroy any other interface (except see #4)
+     * 1. Request for AP or STA will destroy any other interface (except see #4 and #5)
      * 2. Request for P2P will destroy NAN-only
      * 3. Request for NAN will not destroy any interface
      * --
      * 4. No interface will be destroyed for a requested interface of the same type
+     * 5. No interface will be destroyed if one of the requested interfaces already exists
      */
     private boolean allowedToDeleteIfaceTypeForRequestedType(int existingIfaceType,
-            int requestedIfaceType) {
+            int requestedIfaceType, boolean requestedIfaceTypeAlreadyExists) {
+        // rule 5
+        if (requestedIfaceTypeAlreadyExists) {
+            return false;
+        }
+
         // rule 4
         if (existingIfaceType == requestedIfaceType) {
             return false;
@@ -1604,6 +1616,46 @@
     }
 
     /**
+     * Selects the interfaces to delete.
+     *
+     * Rule: select the most recently created interfaces in order.
+     *
+     * @param excessInterfaces Number of interfaces which need to be selected.
+     * @param interfaces Array of interfaces.
+     */
+    private List<WifiIfaceInfo> selectInterfacesToDelete(int excessInterfaces,
+            WifiIfaceInfo[] interfaces) {
+        if (DBG) {
+            Log.d(TAG, "selectInterfacesToDelete: excessInterfaces=" + excessInterfaces
+                    + ", interfaces=" + Arrays.toString(interfaces));
+        }
+
+        boolean lookupError = false;
+        LongSparseArray<WifiIfaceInfo> orderedList = new LongSparseArray(interfaces.length);
+        for (WifiIfaceInfo info : interfaces) {
+            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(info.name);
+            if (cacheEntry == null) {
+                Log.e(TAG,
+                        "selectInterfacesToDelete: can't find cache entry with name=" + info.name);
+                lookupError = true;
+                break;
+            }
+            orderedList.append(cacheEntry.creationTime, info);
+        }
+
+        if (lookupError) {
+            Log.e(TAG, "selectInterfacesToDelete: falling back to arbitary selection");
+            return Arrays.asList(Arrays.copyOf(interfaces, excessInterfaces));
+        } else {
+            List<WifiIfaceInfo> result = new ArrayList<>(excessInterfaces);
+            for (int i = 0; i < excessInterfaces; ++i) {
+                result.add(orderedList.valueAt(orderedList.size() - i - 1));
+            }
+            return result;
+        }
+    }
+
+    /**
      * Performs chip reconfiguration per the input:
      * - Removes the specified interfaces
      * - Reconfigures the chip to the new chip mode (if necessary)
@@ -1850,6 +1902,7 @@
 
         protected LISTENER mListener;
         private Handler mHandler;
+        private boolean mFrontOfQueue;
 
         // override equals & hash to make sure that the container HashSet is unique with respect to
         // the contained listener
@@ -1864,37 +1917,32 @@
         }
 
         void trigger() {
-            mHandler.sendMessage(mHandler.obtainMessage(LISTENER_TRIGGERED));
+            if (mFrontOfQueue) {
+                mHandler.postAtFrontOfQueue(() -> {
+                    action();
+                });
+            } else {
+                mHandler.post(() -> {
+                    action();
+                });
+            }
         }
 
         protected abstract void action();
 
-        ListenerProxy(LISTENER listener, Looper looper, String tag) {
+        ListenerProxy(LISTENER listener, Handler handler, boolean frontOfQueue, String tag) {
             mListener = listener;
-            mHandler = new Handler(looper) {
-                @Override
-                public void handleMessage(Message msg) {
-                    if (DBG) {
-                        Log.d(tag, "ListenerProxy.handleMessage: what=" + msg.what);
-                    }
-                    switch (msg.what) {
-                        case LISTENER_TRIGGERED:
-                            action();
-                            break;
-                        default:
-                            Log.e(tag, "ListenerProxy.handleMessage: unknown message what="
-                                    + msg.what);
-                    }
-                }
-            };
+            mHandler = handler;
+            mFrontOfQueue = frontOfQueue;
         }
     }
 
     private class InterfaceDestroyedListenerProxy extends
             ListenerProxy<InterfaceDestroyedListener> {
         InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener,
-                Looper looper) {
-            super(destroyedListener, looper, "InterfaceDestroyedListenerProxy");
+                Handler handler) {
+            super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
+                    true, "InterfaceDestroyedListenerProxy");
         }
 
         @Override
@@ -1906,8 +1954,9 @@
     private class InterfaceAvailableForRequestListenerProxy extends
             ListenerProxy<InterfaceAvailableForRequestListener> {
         InterfaceAvailableForRequestListenerProxy(
-                InterfaceAvailableForRequestListener destroyedListener, Looper looper) {
-            super(destroyedListener, looper, "InterfaceAvailableForRequestListenerProxy");
+                InterfaceAvailableForRequestListener destroyedListener, Handler handler) {
+            super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
+                    false, "InterfaceAvailableForRequestListenerProxy");
         }
 
         @Override
diff --git a/com/android/server/wifi/SoftApManager.java b/com/android/server/wifi/SoftApManager.java
index d4a1ea5..e045c39 100644
--- a/com/android/server/wifi/SoftApManager.java
+++ b/com/android/server/wifi/SoftApManager.java
@@ -20,8 +20,12 @@
 import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL;
 import static com.android.server.wifi.util.ApConfigUtil.SUCCESS;
 
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
 import android.net.InterfaceConfiguration;
 import android.net.wifi.IApInterface;
+import android.net.wifi.IApInterfaceEventCallback;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiManager;
@@ -29,8 +33,11 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.net.BaseNetworkObserver;
@@ -46,6 +53,8 @@
 public class SoftApManager implements ActiveModeManager {
     private static final String TAG = "SoftApManager";
 
+    private final Context mContext;
+
     private final WifiNative mWifiNative;
 
     private final String mCountryCode;
@@ -55,14 +64,29 @@
     private final Listener mListener;
 
     private final IApInterface mApInterface;
+    private final String mApInterfaceName;
 
     private final INetworkManagementService mNwService;
     private final WifiApConfigStore mWifiApConfigStore;
 
     private final WifiMetrics mWifiMetrics;
 
+    private final int mMode;
     private WifiConfiguration mApConfig;
 
+    private int mNumAssociatedStations = 0;
+
+    /**
+     * Listener for AP Interface events.
+     */
+    public class ApInterfaceListener extends IApInterfaceEventCallback.Stub {
+        @Override
+        public void onNumAssociatedStationsChanged(int numStations) {
+            mStateMachine.sendMessage(
+                    SoftApStateMachine.CMD_NUM_ASSOCIATED_STATIONS_CHANGED, numStations);
+        }
+    }
+
     /**
      * Listener for soft AP state changes.
      */
@@ -75,23 +99,29 @@
         void onStateChanged(int state, int failureReason);
     }
 
-    public SoftApManager(Looper looper,
+    public SoftApManager(Context context,
+                         Looper looper,
                          WifiNative wifiNative,
                          String countryCode,
                          Listener listener,
-                         IApInterface apInterface,
+                         @NonNull IApInterface apInterface,
+                         @NonNull String ifaceName,
                          INetworkManagementService nms,
                          WifiApConfigStore wifiApConfigStore,
-                         WifiConfiguration config,
+                         @NonNull SoftApModeConfiguration apConfig,
                          WifiMetrics wifiMetrics) {
         mStateMachine = new SoftApStateMachine(looper);
 
+        mContext = context;
         mWifiNative = wifiNative;
         mCountryCode = countryCode;
         mListener = listener;
         mApInterface = apInterface;
+        mApInterfaceName = ifaceName;
         mNwService = nms;
         mWifiApConfigStore = wifiApConfigStore;
+        mMode = apConfig.getTargetMode();
+        WifiConfiguration config = apConfig.getWifiConfiguration();
         if (config == null) {
             mApConfig = mWifiApConfigStore.getApConfiguration();
         } else {
@@ -115,14 +145,52 @@
     }
 
     /**
+     * Get number of stations associated with this soft AP
+     */
+    @VisibleForTesting
+    public int getNumAssociatedStations() {
+        return mNumAssociatedStations;
+    }
+
+    /**
+     * Set number of stations associated with this soft AP
+     * @param numStations Number of connected stations
+     */
+    private void setNumAssociatedStations(int numStations) {
+        if (mNumAssociatedStations == numStations) {
+            return;
+        }
+        mNumAssociatedStations = numStations;
+        Log.d(TAG, "Number of associated stations changed: " + mNumAssociatedStations);
+
+        // TODO:(b/63906412) send it up to settings.
+        mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(mNumAssociatedStations, mMode);
+    }
+
+    /**
      * Update AP state.
-     * @param state new AP state
+     * @param newState new AP state
+     * @param currentState current AP state
      * @param reason Failure reason if the new AP state is in failure state
      */
-    private void updateApState(int state, int reason) {
+    private void updateApState(int newState, int currentState, int reason) {
         if (mListener != null) {
-            mListener.onStateChanged(state, reason);
+            mListener.onStateChanged(newState, reason);
         }
+
+        //send the AP state change broadcast
+        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, newState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, currentState);
+        if (newState == WifiManager.WIFI_AP_STATE_FAILED) {
+            //only set reason number when softAP start failed
+            intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
+        }
+
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, mApInterfaceName);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mMode);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     /**
@@ -142,6 +210,7 @@
         int result = ApConfigUtil.updateApChannelConfig(
                 mWifiNative, mCountryCode,
                 mWifiApConfigStore.getAllowed2GChannel(), localConfig);
+
         if (result != SUCCESS) {
             Log.e(TAG, "Failed to update AP band and channel");
             return result;
@@ -180,7 +249,7 @@
                 return ERROR_GENERIC;
             }
 
-            success = mApInterface.startHostapd();
+            success = mApInterface.startHostapd(new ApInterfaceListener());
             if (!success) {
                 Log.e(TAG, "Failed to start hostapd.");
                 return ERROR_GENERIC;
@@ -234,6 +303,7 @@
         public static final int CMD_STOP = 1;
         public static final int CMD_AP_INTERFACE_BINDER_DEATH = 2;
         public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
+        public static final int CMD_NUM_ASSOCIATED_STATIONS_CHANGED = 4;
 
         private final State mIdleState = new IdleState();
         private final State mStartedState = new StartedState();
@@ -280,10 +350,24 @@
             public boolean processMessage(Message message) {
                 switch (message.what) {
                     case CMD_START:
-                        updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0);
+                        // first a sanity check on the interface name.  If we failed to retrieve it,
+                        // we are going to have a hard time setting up routing.
+                        if (TextUtils.isEmpty(mApInterfaceName)) {
+                            Log.e(TAG, "Not starting softap mode without an interface name.");
+                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                          WifiManager.WIFI_AP_STATE_DISABLED,
+                                          WifiManager.SAP_START_FAILURE_GENERAL);
+                            mWifiMetrics.incrementSoftApStartResult(
+                                    false, WifiManager.SAP_START_FAILURE_GENERAL);
+                            break;
+                        }
+                        updateApState(WifiManager.WIFI_AP_STATE_ENABLING,
+                                WifiManager.WIFI_AP_STATE_DISABLED, 0);
+                        setNumAssociatedStations(0);
                         if (!mDeathRecipient.linkToDeath(mApInterface.asBinder())) {
                             mDeathRecipient.unlinkToDeath();
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                    WifiManager.WIFI_AP_STATE_ENABLING,
                                     WifiManager.SAP_START_FAILURE_GENERAL);
                             mWifiMetrics.incrementSoftApStartResult(
                                     false, WifiManager.SAP_START_FAILURE_GENERAL);
@@ -291,12 +375,13 @@
                         }
 
                         try {
-                            mNetworkObserver = new NetworkObserver(mApInterface.getInterfaceName());
+                            mNetworkObserver = new NetworkObserver(mApInterfaceName);
                             mNwService.registerObserver(mNetworkObserver);
                         } catch (RemoteException e) {
                             mDeathRecipient.unlinkToDeath();
                             unregisterObserver();
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                          WifiManager.WIFI_AP_STATE_ENABLING,
                                           WifiManager.SAP_START_FAILURE_GENERAL);
                             mWifiMetrics.incrementSoftApStartResult(
                                     false, WifiManager.SAP_START_FAILURE_GENERAL);
@@ -311,7 +396,9 @@
                             }
                             mDeathRecipient.unlinkToDeath();
                             unregisterObserver();
-                            updateApState(WifiManager.WIFI_AP_STATE_FAILED, failureReason);
+                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                          WifiManager.WIFI_AP_STATE_ENABLING,
+                                          failureReason);
                             mWifiMetrics.incrementSoftApStartResult(false, failureReason);
                             break;
                         }
@@ -347,11 +434,15 @@
                 mIfaceIsUp = isUp;
                 if (isUp) {
                     Log.d(TAG, "SoftAp is ready for use");
-                    updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0);
+                    updateApState(WifiManager.WIFI_AP_STATE_ENABLED,
+                            WifiManager.WIFI_AP_STATE_ENABLING, 0);
                     mWifiMetrics.incrementSoftApStartResult(true, 0);
                 } else {
                     // TODO: handle the case where the interface was up, but goes down
                 }
+
+                mWifiMetrics.addSoftApUpChangedEvent(isUp, mMode);
+                setNumAssociatedStations(0);
             }
 
             @Override
@@ -359,7 +450,7 @@
                 mIfaceIsUp = false;
                 InterfaceConfiguration config = null;
                 try {
-                    config = mNwService.getInterfaceConfig(mApInterface.getInterfaceName());
+                    config = mNwService.getInterfaceConfig(mApInterfaceName);
                 } catch (RemoteException e) {
                 }
                 if (config != null) {
@@ -370,6 +461,13 @@
             @Override
             public boolean processMessage(Message message) {
                 switch (message.what) {
+                    case CMD_NUM_ASSOCIATED_STATIONS_CHANGED:
+                        if (message.arg1 < 0) {
+                            Log.e(TAG, "Invalid number of associated stations: " + message.arg1);
+                            break;
+                        }
+                        setNumAssociatedStations(message.arg1);
+                        break;
                     case CMD_INTERFACE_STATUS_CHANGED:
                         if (message.obj != mNetworkObserver) {
                             // This is from some time before the most recent configuration.
@@ -383,15 +481,23 @@
                         break;
                     case CMD_AP_INTERFACE_BINDER_DEATH:
                     case CMD_STOP:
-                        updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 0);
+                        updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
+                                WifiManager.WIFI_AP_STATE_ENABLED, 0);
+                        setNumAssociatedStations(0);
                         stopSoftAp();
                         if (message.what == CMD_AP_INTERFACE_BINDER_DEATH) {
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
-                                    WifiManager.SAP_START_FAILURE_GENERAL);
+                                          WifiManager.WIFI_AP_STATE_DISABLING,
+                                          WifiManager.SAP_START_FAILURE_GENERAL);
                         } else {
-                            updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0);
+                            updateApState(WifiManager.WIFI_AP_STATE_DISABLED,
+                                    WifiManager.WIFI_AP_STATE_DISABLING, 0);
                         }
                         transitionTo(mIdleState);
+
+                        // Need this here since we are exiting |Started| state and won't handle any
+                        // future CMD_INTERFACE_STATUS_CHANGED events after this point
+                        mWifiMetrics.addSoftApUpChangedEvent(false, mMode);
                         break;
                     default:
                         return NOT_HANDLED;
@@ -399,6 +505,5 @@
                 return HANDLED;
             }
         }
-
     }
 }
diff --git a/com/android/server/wifi/SupplicantStaIfaceHal.java b/com/android/server/wifi/SupplicantStaIfaceHal.java
index c300c3a..c93e110 100644
--- a/com/android/server/wifi/SupplicantStaIfaceHal.java
+++ b/com/android/server/wifi/SupplicantStaIfaceHal.java
@@ -1938,7 +1938,6 @@
     }
 
     private class SupplicantStaIfaceHalCallback extends ISupplicantStaIfaceCallback.Stub {
-        private static final int WLAN_REASON_IE_IN_4WAY_DIFFERS = 17; // IEEE 802.11i
         private boolean mStateIsFourway = false; // Used to help check for PSK password mismatch
 
         /**
@@ -2083,10 +2082,14 @@
                             + " reasonCode=" + reasonCode);
                 }
                 if (mStateIsFourway
-                        && (!locallyGenerated || reasonCode != WLAN_REASON_IE_IN_4WAY_DIFFERS)) {
+                        && (!locallyGenerated || reasonCode != ReasonCode.IE_IN_4WAY_DIFFERS)) {
                     mWifiMonitor.broadcastAuthenticationFailureEvent(
                             mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
                 }
+                if (reasonCode == ReasonCode.IEEE_802_1X_AUTH_FAILED) {
+                    mWifiMonitor.broadcastAuthenticationFailureEvent(
+                            mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE);
+                }
                 mWifiMonitor.broadcastNetworkDisconnectionEvent(
                         mIfaceName, locallyGenerated ? 1 : 0, reasonCode,
                         NativeUtil.macAddressFromByteArray(bssid));
diff --git a/com/android/server/wifi/VelocityBasedConnectedScore.java b/com/android/server/wifi/VelocityBasedConnectedScore.java
index 9d90332..69643ce 100644
--- a/com/android/server/wifi/VelocityBasedConnectedScore.java
+++ b/com/android/server/wifi/VelocityBasedConnectedScore.java
@@ -34,7 +34,8 @@
     private final int mThresholdMinimumRssi24;     // -85
 
     private int mFrequency = 5000;
-    private int mRssi = 0;
+    private double mThresholdMinimumRssi;
+    private double mThresholdAdjustment;
     private final KalmanFilter mFilter;
     private long mLastMillis;
 
@@ -44,6 +45,7 @@
                 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
         mThresholdMinimumRssi24 = context.getResources().getInteger(
                 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mThresholdMinimumRssi = mThresholdMinimumRssi5;
         mFilter = new KalmanFilter();
         mFilter.mH = new Matrix(2, new double[]{1.0, 0.0});
         mFilter.mR = new Matrix(1, new double[]{1.0});
@@ -69,6 +71,7 @@
     @Override
     public void reset() {
         mLastMillis = 0;
+        mThresholdAdjustment = 0.0;
     }
 
     /**
@@ -97,6 +100,8 @@
         mFilter.predict();
         mLastMillis = millis;
         mFilter.update(new Matrix(1, new double[]{rssi}));
+        mFilteredRssi = mFilter.mx.get(0, 0);
+        mEstimatedRateOfRssiChange = mFilter.mx.get(1, 0);
     }
 
     /**
@@ -106,10 +111,63 @@
     public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
         int frequency = wifiInfo.getFrequency();
         if (frequency != mFrequency) {
-            reset(); // Probably roamed
+            mLastMillis = 0; // Probably roamed; reset filter but retain threshold adjustment
+            // Consider resetting or partially resetting threshold adjustment
+            // Consider checking bssid
             mFrequency = frequency;
+            mThresholdMinimumRssi =
+                    mFrequency >= 5000 ? mThresholdMinimumRssi5 : mThresholdMinimumRssi24;
         }
         updateUsingRssi(wifiInfo.getRssi(), millis, mDefaultRssiStandardDeviation);
+        adjustThreshold(wifiInfo);
+    }
+
+    private double mFilteredRssi;
+    private double mEstimatedRateOfRssiChange;
+
+    /**
+     * Returns the most recently computed extimate of the RSSI.
+     */
+    public double getFilteredRssi() {
+        return mFilteredRssi;
+    }
+
+    /**
+     * Returns the estimated rate of change of RSSI, in dB/second
+     */
+    public double getEstimatedRateOfRssiChange() {
+        return mEstimatedRateOfRssiChange;
+    }
+
+    /**
+     * Returns the adjusted RSSI threshold
+     */
+    public double getAdjustedRssiThreshold() {
+        return mThresholdMinimumRssi + mThresholdAdjustment;
+    }
+
+    /**
+     * Adjusts the threshold if appropriate
+     * <p>
+     * If the (filtered) rssi is near or below the current effective threshold, and the
+     * rate of rssi change is small, and there is traffic, and the error rate is looking
+     * reasonable, then decrease the effective threshold to keep from dropping a perfectly good
+     * connection.
+     *
+     */
+    private void adjustThreshold(WifiInfo wifiInfo) {
+        if (mThresholdAdjustment < -7) return;
+        if (mFilteredRssi >= getAdjustedRssiThreshold() + 2.0) return;
+        if (Math.abs(mEstimatedRateOfRssiChange) >= 0.2) return;
+        if (wifiInfo.txSuccessRate < 10) return;
+        if (wifiInfo.rxSuccessRate < 10) return;
+        double probabilityOfSuccessfulTx = (
+                wifiInfo.txSuccessRate / (wifiInfo.txSuccessRate + wifiInfo.txBadRate)
+        );
+        if (probabilityOfSuccessfulTx >= 0.2) {
+            // May want this amount to vary with how close to threshold we are
+            mThresholdAdjustment -= 0.5;
+        }
     }
 
     /**
@@ -117,7 +175,7 @@
      */
     @Override
     public int generateScore() {
-        int badRssi = mFrequency >= 5000 ? mThresholdMinimumRssi5 : mThresholdMinimumRssi24;
+        double badRssi = getAdjustedRssiThreshold();
         double horizonSeconds = 15.0;
         Matrix x = new Matrix(mFilter.mx);
         double filteredRssi = x.get(0, 0);
diff --git a/com/android/server/wifi/WifiConfigManager.java b/com/android/server/wifi/WifiConfigManager.java
index b610bd9..8fb61c2 100644
--- a/com/android/server/wifi/WifiConfigManager.java
+++ b/com/android/server/wifi/WifiConfigManager.java
@@ -200,12 +200,6 @@
     @VisibleForTesting
     public static final int LINK_CONFIGURATION_BSSID_MATCH_LENGTH = 16;
     /**
-     * Flags to be passed in for |canModifyNetwork| to decide if the change is minor and can
-     * bypass the lockdown checks.
-     */
-    private static final boolean ALLOW_LOCKDOWN_CHECK_BYPASS = true;
-    private static final boolean DISALLOW_LOCKDOWN_CHECK_BYPASS = false;
-    /**
      * Log tag for this class.
      */
     private static final String TAG = "WifiConfigManager";
@@ -646,9 +640,8 @@
      *
      * @param config         WifiConfiguration object corresponding to the network to be modified.
      * @param uid            UID of the app requesting the modification.
-     * @param ignoreLockdown Ignore the configuration lockdown checks for connection attempts.
      */
-    private boolean canModifyNetwork(WifiConfiguration config, int uid, boolean ignoreLockdown) {
+    private boolean canModifyNetwork(WifiConfiguration config, int uid) {
         // System internals can always update networks; they're typically only
         // making meteredHint or meteredOverride changes
         if (uid == Process.SYSTEM_UID) {
@@ -685,12 +678,6 @@
 
         final boolean isCreator = (config.creatorUid == uid);
 
-        // Check if the |uid| holds the |NETWORK_SETTINGS| permission if the caller asks us to
-        // bypass the lockdown checks.
-        if (ignoreLockdown) {
-            return mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
-        }
-
         // Check if device has DPM capability. If it has and |dpmi| is still null, then we
         // treat this case with suspicion and bail out.
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)
@@ -980,7 +967,7 @@
                 return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
             }
             // Check for the app's permission before we let it update this network.
-            if (!canModifyNetwork(existingInternalConfig, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+            if (!canModifyNetwork(existingInternalConfig, uid)) {
                 Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
                         + config.configKey());
                 return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
@@ -1150,7 +1137,7 @@
         if (config == null) {
             return false;
         }
-        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+        if (!canModifyNetwork(config, uid)) {
             Log.e(TAG, "UID " + uid + " does not have permission to delete configuration "
                     + config.configKey());
             return false;
@@ -1483,7 +1470,7 @@
         if (config == null) {
             return false;
         }
-        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+        if (!canModifyNetwork(config, uid)) {
             Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
                     + config.configKey());
             return false;
@@ -1518,7 +1505,7 @@
         if (config == null) {
             return false;
         }
-        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+        if (!canModifyNetwork(config, uid)) {
             Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
                     + config.configKey());
             return false;
@@ -1535,18 +1522,13 @@
     }
 
     /**
-     * Checks if the |uid| has the necessary permission to force a connection to a network
-     * and updates the last connected UID for the provided configuration.
+     * Updates the last connected UID for the provided configuration.
      *
      * @param networkId network ID corresponding to the network.
      * @param uid       uid of the app requesting the connection.
-     * @return true if |uid| has the necessary permission to trigger explicit connection to the
-     * network, false otherwise.
-     * Note: This returns true only for the system settings/sysui app which holds the
-     * {@link android.Manifest.permission#NETWORK_SETTINGS} permission. We don't want to let
-     * any other app force connection to a network.
+     * @return true if the network was found, false otherwise.
      */
-    public boolean checkAndUpdateLastConnectUid(int networkId, int uid) {
+    public boolean updateLastConnectUid(int networkId, int uid) {
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Update network last connect UID for " + networkId);
         }
@@ -1558,11 +1540,6 @@
         if (config == null) {
             return false;
         }
-        if (!canModifyNetwork(config, uid, ALLOW_LOCKDOWN_CHECK_BYPASS)) {
-            Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
-                    + config.configKey());
-            return false;
-        }
         config.lastConnectUid = uid;
         return true;
     }
diff --git a/com/android/server/wifi/WifiInjector.java b/com/android/server/wifi/WifiInjector.java
index 1cbb29e..cc55934 100644
--- a/com/android/server/wifi/WifiInjector.java
+++ b/com/android/server/wifi/WifiInjector.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.net.NetworkKey;
@@ -23,7 +24,6 @@
 import android.net.wifi.IApInterface;
 import android.net.wifi.IWifiScanner;
 import android.net.wifi.IWificond;
-import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.WifiScanner;
@@ -163,7 +163,7 @@
         mWifiMetrics = new WifiMetrics(mClock, wifiStateMachineLooper, awareMetrics);
         // Modules interacting with Native.
         mWifiMonitor = new WifiMonitor(this);
-        mHalDeviceManager = new HalDeviceManager();
+        mHalDeviceManager = new HalDeviceManager(mClock);
         mWifiVendorHal =
                 new WifiVendorHal(mHalDeviceManager, mWifiStateMachineHandlerThread.getLooper());
         mSupplicantStaIfaceHal = new SupplicantStaIfaceHal(mContext, mWifiMonitor);
@@ -370,16 +370,18 @@
      * changes
      * @param listener listener for SoftApManager
      * @param apInterface network interface to start hostapd against
-     * @param config softAp WifiConfiguration
+     * @param ifaceName name of the ap interface
+     * @param config SoftApModeConfiguration object holding the config and mode
      * @return an instance of SoftApManager
      */
     public SoftApManager makeSoftApManager(INetworkManagementService nmService,
                                            SoftApManager.Listener listener,
-                                           IApInterface apInterface,
-                                           WifiConfiguration config) {
-        return new SoftApManager(mWifiServiceHandlerThread.getLooper(),
+                                           @NonNull IApInterface apInterface,
+                                           @NonNull String ifaceName,
+                                           @NonNull SoftApModeConfiguration config) {
+        return new SoftApManager(mContext, mWifiServiceHandlerThread.getLooper(),
                                  mWifiNative, mCountryCode.getCountryCode(),
-                                 listener, apInterface, nmService,
+                                 listener, apInterface, ifaceName, nmService,
                                  mWifiApConfigStore, config, mWifiMetrics);
     }
 
diff --git a/com/android/server/wifi/WifiMetrics.java b/com/android/server/wifi/WifiMetrics.java
index 7254ad4..4e277a1 100644
--- a/com/android/server/wifi/WifiMetrics.java
+++ b/com/android/server/wifi/WifiMetrics.java
@@ -31,6 +31,7 @@
 import android.util.Pair;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.aware.WifiAwareMetrics;
 import com.android.server.wifi.hotspot2.ANQPNetworkKey;
 import com.android.server.wifi.hotspot2.NetworkDetail;
@@ -41,8 +42,10 @@
 import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
+import com.android.server.wifi.nano.WifiMetricsProto.SoftApConnectedClientsEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
+import com.android.server.wifi.nano.WifiMetricsProto.WpsMetrics;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.ScanResultUtil;
 
@@ -80,6 +83,8 @@
     public static final long TIMEOUT_RSSI_DELTA_MILLIS =  3000;
     private static final int MIN_WIFI_SCORE = 0;
     private static final int MAX_WIFI_SCORE = NetworkAgent.WIFI_BASE_SCORE;
+    @VisibleForTesting
+    static final int LOW_WIFI_SCORE = 50; // Mobile data score
     private final Object mLock = new Object();
     private static final int MAX_CONNECTION_EVENTS = 256;
     // Largest bucket in the NumConnectableNetworkCount histogram,
@@ -92,11 +97,14 @@
     public static final int MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET = 20;
     public static final int MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET = 50;
     private static final int CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER = 1000;
+    // Max limit for number of soft AP related events, extra events will be dropped.
+    private static final int MAX_NUM_SOFT_AP_EVENTS = 256;
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
     private WifiAwareMetrics mWifiAwareMetrics;
     private final PnoScanMetrics mPnoScanMetrics = new PnoScanMetrics();
+    private final WpsMetrics mWpsMetrics = new WpsMetrics();
     private Handler mHandler;
     private WifiConfigManager mWifiConfigManager;
     private WifiNetworkSelector mWifiNetworkSelector;
@@ -167,6 +175,10 @@
     private boolean mIsWifiNetworksAvailableNotificationOn = false;
     private int mNumOpenNetworkConnectMessageFailedToSend = 0;
     private int mNumOpenNetworkRecommendationUpdates = 0;
+    /** List of soft AP events related to number of connected clients in tethered mode */
+    private final List<SoftApConnectedClientsEvent> mSoftApEventListTethered = new ArrayList<>();
+    /** List of soft AP events related to number of connected clients in local only mode */
+    private final List<SoftApConnectedClientsEvent> mSoftApEventListLocalOnly = new ArrayList<>();
 
     private final SparseIntArray mObservedHotspotR1ApInScanHistogram = new SparseIntArray();
     private final SparseIntArray mObservedHotspotR2ApInScanHistogram = new SparseIntArray();
@@ -479,6 +491,78 @@
         }
     }
 
+    /**
+     * Increment total number of wps connection attempts
+     */
+    public void incrementWpsAttemptCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsAttempts++;
+        }
+    }
+
+    /**
+     * Increment total number of wps connection success
+     */
+    public void incrementWpsSuccessCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsSuccess++;
+        }
+    }
+
+    /**
+     * Increment total number of wps failure on start
+     */
+    public void incrementWpsStartFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsStartFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of wps overlap failure
+     */
+    public void incrementWpsOverlapFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsOverlapFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of wps timeout failure
+     */
+    public void incrementWpsTimeoutFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsTimeoutFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of other wps failure during connection
+     */
+    public void incrementWpsOtherConnectionFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsOtherConnectionFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of supplicant failure after wps
+     */
+    public void incrementWpsSupplicantFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsSupplicantFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of wps cancellation
+     */
+    public void incrementWpsCancellationCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsCancellation++;
+        }
+    }
+
     // Values used for indexing SystemStateEntries
     private static final int SCREEN_ON = 1;
     private static final int SCREEN_OFF = 0;
@@ -1055,10 +1139,14 @@
         }
     }
 
+    private boolean mWifiWins = false; // Based on scores, use wifi instead of mobile data?
+
     /**
      * Increments occurence of a particular wifi score calculated
      * in WifiScoreReport by current connected network. Scores are bounded
-     * within  [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray
+     * within  [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray.
+     *
+     * Also records events when the current score breaches significant thresholds.
      */
     public void incrementWifiScoreCount(int score) {
         if (score < MIN_WIFI_SCORE || score > MAX_WIFI_SCORE) {
@@ -1067,6 +1155,20 @@
         synchronized (mLock) {
             int count = mWifiScoreCounts.get(score);
             mWifiScoreCounts.put(score, count + 1);
+
+            boolean wifiWins = mWifiWins;
+            if (mWifiWins && score < LOW_WIFI_SCORE) {
+                wifiWins = false;
+            } else if (!mWifiWins && score > LOW_WIFI_SCORE) {
+                wifiWins = true;
+            }
+            mLastScore = score;
+            if (wifiWins != mWifiWins) {
+                mWifiWins = wifiWins;
+                StaEvent event = new StaEvent();
+                event.type = StaEvent.TYPE_SCORE_BREACH;
+                addStaEvent(event);
+            }
         }
     }
 
@@ -1106,6 +1208,53 @@
     }
 
     /**
+     * Adds a record indicating the current up state of soft AP
+     */
+    public void addSoftApUpChangedEvent(boolean isUp, int mode) {
+        SoftApConnectedClientsEvent event = new SoftApConnectedClientsEvent();
+        event.eventType = isUp ? SoftApConnectedClientsEvent.SOFT_AP_UP :
+                SoftApConnectedClientsEvent.SOFT_AP_DOWN;
+        event.numConnectedClients = 0;
+        addSoftApConnectedClientsEvent(event, mode);
+    }
+
+    /**
+     * Adds a record for current number of associated stations to soft AP
+     */
+    public void addSoftApNumAssociatedStationsChangedEvent(int numStations, int mode) {
+        SoftApConnectedClientsEvent event = new SoftApConnectedClientsEvent();
+        event.eventType = SoftApConnectedClientsEvent.NUM_CLIENTS_CHANGED;
+        event.numConnectedClients = numStations;
+        addSoftApConnectedClientsEvent(event, mode);
+    }
+
+    /**
+     * Adds a record to the corresponding event list based on mode param
+     */
+    private void addSoftApConnectedClientsEvent(SoftApConnectedClientsEvent event, int mode) {
+        synchronized (mLock) {
+            List<SoftApConnectedClientsEvent> softApEventList;
+            switch (mode) {
+                case WifiManager.IFACE_IP_MODE_TETHERED:
+                    softApEventList = mSoftApEventListTethered;
+                    break;
+                case WifiManager.IFACE_IP_MODE_LOCAL_ONLY:
+                    softApEventList = mSoftApEventListLocalOnly;
+                    break;
+                default:
+                    return;
+            }
+
+            if (softApEventList.size() > MAX_NUM_SOFT_AP_EVENTS) {
+                return;
+            }
+
+            event.timeStampMillis = mClock.getWallClockMillis();
+            softApEventList.add(event);
+        }
+    }
+
+    /**
      * Increment number of times the HAL crashed.
      */
     public void incrementNumHalCrashes() {
@@ -1634,6 +1783,40 @@
                         + mObservedHotspotR1ApsPerEssInScanHistogram);
                 pw.println("mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram="
                         + mObservedHotspotR2ApsPerEssInScanHistogram);
+
+                pw.println("mSoftApTetheredEvents:");
+                for (SoftApConnectedClientsEvent event : mSoftApEventListTethered) {
+                    StringBuilder eventLine = new StringBuilder();
+                    eventLine.append("event_type=" + event.eventType);
+                    eventLine.append(",time_stamp_millis=" + event.timeStampMillis);
+                    eventLine.append(",num_connected_clients=" + event.numConnectedClients);
+                    pw.println(eventLine.toString());
+                }
+                pw.println("mSoftApLocalOnlyEvents:");
+                for (SoftApConnectedClientsEvent event : mSoftApEventListLocalOnly) {
+                    StringBuilder eventLine = new StringBuilder();
+                    eventLine.append("event_type=" + event.eventType);
+                    eventLine.append(",time_stamp_millis=" + event.timeStampMillis);
+                    eventLine.append(",num_connected_clients=" + event.numConnectedClients);
+                    pw.println(eventLine.toString());
+                }
+
+                pw.println("mWpsMetrics.numWpsAttempts="
+                        + mWpsMetrics.numWpsAttempts);
+                pw.println("mWpsMetrics.numWpsSuccess="
+                        + mWpsMetrics.numWpsSuccess);
+                pw.println("mWpsMetrics.numWpsStartFailure="
+                        + mWpsMetrics.numWpsStartFailure);
+                pw.println("mWpsMetrics.numWpsOverlapFailure="
+                        + mWpsMetrics.numWpsOverlapFailure);
+                pw.println("mWpsMetrics.numWpsTimeoutFailure="
+                        + mWpsMetrics.numWpsTimeoutFailure);
+                pw.println("mWpsMetrics.numWpsOtherConnectionFailure="
+                        + mWpsMetrics.numWpsOtherConnectionFailure);
+                pw.println("mWpsMetrics.numWpsSupplicantFailure="
+                        + mWpsMetrics.numWpsSupplicantFailure);
+                pw.println("mWpsMetrics.numWpsCancellation="
+                        + mWpsMetrics.numWpsCancellation);
             }
         }
     }
@@ -1906,6 +2089,19 @@
             mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram =
                     makeNumConnectableNetworksBucketArray(
                             mObservedHotspotR2ApsPerEssInScanHistogram);
+
+            if (mSoftApEventListTethered.size() > 0) {
+                mWifiLogProto.softApConnectedClientsEventsTethered =
+                        mSoftApEventListTethered.toArray(
+                        mWifiLogProto.softApConnectedClientsEventsTethered);
+            }
+            if (mSoftApEventListLocalOnly.size() > 0) {
+                mWifiLogProto.softApConnectedClientsEventsLocalOnly =
+                        mSoftApEventListLocalOnly.toArray(
+                        mWifiLogProto.softApConnectedClientsEventsLocalOnly);
+            }
+
+            mWifiLogProto.wpsMetrics = mWpsMetrics;
         }
     }
 
@@ -1966,6 +2162,9 @@
             mObservedHotspotR2EssInScanHistogram.clear();
             mObservedHotspotR1ApsPerEssInScanHistogram.clear();
             mObservedHotspotR2ApsPerEssInScanHistogram.clear();
+            mSoftApEventListTethered.clear();
+            mSoftApEventListLocalOnly.clear();
+            mWpsMetrics.clear();
         }
     }
 
@@ -1984,6 +2183,7 @@
     public void setWifiState(int wifiState) {
         synchronized (mLock) {
             mWifiState = wifiState;
+            mWifiWins = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
         }
     }
 
@@ -2089,6 +2289,7 @@
             case StaEvent.TYPE_CONNECT_NETWORK:
             case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK:
             case StaEvent.TYPE_FRAMEWORK_DISCONNECT:
+            case StaEvent.TYPE_SCORE_BREACH:
                 break;
             default:
                 Log.e(TAG, "Unknown StaEvent:" + type);
@@ -2109,10 +2310,12 @@
         staEvent.lastFreq = mLastPollFreq;
         staEvent.lastLinkSpeed = mLastPollLinkSpeed;
         staEvent.supplicantStateChangesBitmask = mSupplicantStateChangeBitmask;
+        staEvent.lastScore = mLastScore;
         mSupplicantStateChangeBitmask = 0;
         mLastPollRssi = -127;
         mLastPollFreq = -1;
         mLastPollLinkSpeed = -1;
+        mLastScore = -1;
         mStaEventList.add(new StaEventWithTime(staEvent, mClock.getWallClockMillis()));
         // Prune StaEventList if it gets too long
         if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove();
@@ -2268,6 +2471,9 @@
                         .append(" reason=")
                         .append(frameworkDisconnectReasonToString(event.frameworkDisconnectReason));
                 break;
+            case StaEvent.TYPE_SCORE_BREACH:
+                sb.append("SCORE_BREACH");
+                break;
             default:
                 sb.append("UNKNOWN " + event.type + ":");
                 break;
@@ -2275,6 +2481,7 @@
         if (event.lastRssi != -127) sb.append(" lastRssi=").append(event.lastRssi);
         if (event.lastFreq != -1) sb.append(" lastFreq=").append(event.lastFreq);
         if (event.lastLinkSpeed != -1) sb.append(" lastLinkSpeed=").append(event.lastLinkSpeed);
+        if (event.lastScore != -1) sb.append(" lastScore=").append(event.lastScore);
         if (event.supplicantStateChangesBitmask != 0) {
             sb.append(", ").append(supplicantStateChangesBitmaskToString(
                     event.supplicantStateChangesBitmask));
@@ -2337,11 +2544,12 @@
         return sb.toString();
     }
 
-    public static final int MAX_STA_EVENTS = 512;
+    public static final int MAX_STA_EVENTS = 768;
     private LinkedList<StaEventWithTime> mStaEventList = new LinkedList<StaEventWithTime>();
     private int mLastPollRssi = -127;
     private int mLastPollLinkSpeed = -1;
     private int mLastPollFreq = -1;
+    private int mLastScore = -1;
 
     /**
      * Converts the first 31 bits of a BitSet to a little endian int
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index eefd2f0..cda1cf6 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -213,6 +213,22 @@
     }
 
     /**
+     * Query the list of valid frequencies for the provided band.
+     * The result depends on the on the country code that has been set.
+     *
+     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+     * The following bands are supported:
+     * WifiScanner.WIFI_BAND_24_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
+     * @return frequencies vector of valid frequencies (MHz), or null for error.
+     * @throws IllegalArgumentException if band is not recognized.
+     */
+    public int [] getChannelsForBand(int band) {
+        return mWificondControl.getChannelsForBand(band);
+    }
+
+    /**
      * Start a scan using wificond for the given parameters.
      * @param freqs list of frequencies to scan for, if null scan all supported channels.
      * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
@@ -1135,27 +1151,6 @@
     }
 
     /**
-     * Query the list of valid frequencies for the provided band.
-     * The result depends on the on the country code that has been set.
-     *
-     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
-     * @return frequencies vector of valid frequencies (MHz), or null for error.
-     * @throws IllegalArgumentException if band is not recognized.
-     */
-    public int [] getChannelsForBand(int band) {
-        return mWifiVendorHal.getChannelsForBand(band);
-    }
-
-    /**
-     * Indicates whether getChannelsForBand is supported.
-     *
-     * @return true if it is.
-     */
-    public boolean isGetChannelsForBandSupported() {
-        return mWifiVendorHal.isGetChannelsForBandSupported();
-    }
-
-    /**
      * RTT (Round Trip Time) measurement capabilities of the device.
      */
     public RttManager.RttCapabilities getRttCapabilities() {
diff --git a/com/android/server/wifi/WifiScoreReport.java b/com/android/server/wifi/WifiScoreReport.java
index 324cdba..e5281ef 100644
--- a/com/android/server/wifi/WifiScoreReport.java
+++ b/com/android/server/wifi/WifiScoreReport.java
@@ -49,7 +49,7 @@
 
     ConnectedScore mConnectedScore;
     ConnectedScore mAggressiveConnectedScore;
-    ConnectedScore mFancyConnectedScore;
+    VelocityBasedConnectedScore mFancyConnectedScore;
 
     WifiScoreReport(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
         mClock = clock;
@@ -127,9 +127,9 @@
         int s2 = mFancyConnectedScore.generateScore();
 
         if (aggressiveHandover == 0) {
-            score = s0;
-        } else {
             score = s2;
+        } else {
+            score = s2; // TODO Remove aggressive handover plumbing (b/27877641)
         }
 
         //sanitize boundaries
@@ -171,25 +171,31 @@
     private void logLinkMetrics(WifiInfo wifiInfo, long now, int s0, int s1, int s2) {
         if (now < FIRST_REASONABLE_WALL_CLOCK) return;
         double rssi = wifiInfo.getRssi();
+        double filteredRssi = mFancyConnectedScore.getFilteredRssi();
+        double rssiThreshold = mFancyConnectedScore.getAdjustedRssiThreshold();
         int freq = wifiInfo.getFrequency();
         int linkSpeed = wifiInfo.getLinkSpeed();
         double txSuccessRate = wifiInfo.txSuccessRate;
         double txRetriesRate = wifiInfo.txRetriesRate;
         double txBadRate = wifiInfo.txBadRate;
         double rxSuccessRate = wifiInfo.rxSuccessRate;
+        String s;
         try {
             String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
-            String s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
-                    "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d",
-                    timestamp, mSessionNumber, rssi, freq, linkSpeed,
+            s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
+                    "%s,%d,%.1f,%.1f,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d",
+                    timestamp, mSessionNumber, rssi, filteredRssi, rssiThreshold, freq, linkSpeed,
                     txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
                     s0, s1, s2);
-            mLinkMetricsHistory.add(s);
         } catch (Exception e) {
             Log.e(TAG, "format problem", e);
+            return;
         }
-        while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
-            mLinkMetricsHistory.removeFirst();
+        synchronized (mLinkMetricsHistory) {
+            mLinkMetricsHistory.add(s);
+            while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
+                mLinkMetricsHistory.removeFirst();
+            }
         }
     }
 
@@ -205,9 +211,15 @@
      * @param args unused
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1,s2");
-        for (String line : mLinkMetricsHistory) {
+        LinkedList<String> history;
+        synchronized (mLinkMetricsHistory) {
+            history = new LinkedList<>(mLinkMetricsHistory);
+        }
+        pw.println("time,session,rssi,filtered_rssi,rssi_threshold,"
+                + "freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1,s2");
+        for (String line : history) {
             pw.println(line);
         }
+        history.clear();
     }
 }
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index dacc2d4..e86bd54 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
@@ -42,6 +43,7 @@
 import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
 
 import android.Manifest;
+import android.annotation.CheckResult;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningAppProcessInfo;
 import android.app.AppOpsManager;
@@ -589,7 +591,9 @@
      */
     @Override
     public void startScan(ScanSettings settings, WorkSource workSource, String packageName) {
-        enforceChangePermission();
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return;
+        }
 
         mLog.info("startScan uid=%").c(Binder.getCallingUid()).flush();
         // Check and throttle background apps for wifi scan.
@@ -733,9 +737,21 @@
                 "WifiService");
     }
 
-    private void enforceChangePermission() {
+    /**
+     * Checks whether the caller can change the wifi state.
+     * Possible results:
+     * 1. Operation is allowed. No exception thrown, and AppOpsManager.MODE_ALLOWED returned.
+     * 2. Operation is not allowed, and caller must be told about this. SecurityException is thrown.
+     * 3. Operation is not allowed, and caller must not be told about this (i.e. must silently
+     * ignore the operation). No exception is thrown, and AppOpsManager.MODE_IGNORED returned.
+     */
+    @CheckResult
+    private int enforceChangePermission(String callingPackage) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
                 "WifiService");
+
+        return mAppOps.noteOp(AppOpsManager.OP_CHANGE_WIFI_STATE, Binder.getCallingUid(),
+                callingPackage);
     }
 
     private void enforceLocationHardwarePermission() {
@@ -779,7 +795,10 @@
     @Override
     public synchronized boolean setWifiEnabled(String packageName, boolean enable)
             throws RemoteException {
-        enforceChangePermission();
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return false;
+        }
+
         Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid() + ", package=" + packageName);
         mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName)
@@ -1173,7 +1192,9 @@
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
 
-        enforceChangePermission();
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return LocalOnlyHotspotCallback.ERROR_GENERIC;
+        }
         enforceLocationPermission(packageName, uid);
         // also need to verify that Locations services are enabled.
         if (mSettingsStore.getLocationModeSetting(mContext) == Settings.Secure.LOCATION_MODE_OFF) {
@@ -1245,9 +1266,12 @@
      * Hotspot.
      */
     @Override
-    public void stopLocalOnlyHotspot() {
+    public void stopLocalOnlyHotspot(String packageName) {
         // first check if the caller has permission to stop a local only hotspot
-        enforceChangePermission();
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            // As this step is about cleaning up previously allocated resources, we'll allow the
+            // app to do this cleanup even if the op is configured to be ignored.
+        }
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
 
@@ -1349,8 +1373,10 @@
      * @throws SecurityException if the caller does not have permission to write the sotap config
      */
     @Override
-    public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
-        enforceChangePermission();
+    public void setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return;
+        }
         int uid = Binder.getCallingUid();
         // only allow Settings UI to write the stored SoftApConfig
         if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) {
@@ -1382,8 +1408,10 @@
      * see {@link android.net.wifi.WifiManager#disconnect()}
      */
     @Override
-    public void disconnect() {
-        enforceChangePermission();
+    public void disconnect(String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return;
+        }
         mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.disconnectCommand();
     }
@@ -1392,8 +1420,10 @@
      * see {@link android.net.wifi.WifiManager#reconnect()}
      */
     @Override
-    public void reconnect() {
-        enforceChangePermission();
+    public void reconnect(String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return;
+        }
         mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid()));
     }
@@ -1402,8 +1432,10 @@
      * see {@link android.net.wifi.WifiManager#reassociate()}
      */
     @Override
-    public void reassociate() {
-        enforceChangePermission();
+    public void reassociate(String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return;
+        }
         mLog.info("reassociate uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.reassociateCommand();
     }
@@ -1604,8 +1636,10 @@
      * network if the operation succeeds, or {@code -1} if it fails
      */
     @Override
-    public int addOrUpdateNetwork(WifiConfiguration config) {
-        enforceChangePermission();
+    public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return -1;
+        }
         mLog.info("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush();
 
         // Previously, this API is overloaded for installing Passpoint profiles.  Now
@@ -1624,7 +1658,7 @@
                     config.enterpriseConfig.getClientCertificateChain());
             passpointConfig.getCredential().setClientPrivateKey(
                     config.enterpriseConfig.getClientPrivateKey());
-            if (!addOrUpdatePasspointConfiguration(passpointConfig)) {
+            if (!addOrUpdatePasspointConfiguration(passpointConfig, packageName)) {
                 Slog.e(TAG, "Failed to add Passpoint profile");
                 return -1;
             }
@@ -1675,8 +1709,10 @@
      * @return {@code true} if the operation succeeded
      */
     @Override
-    public boolean removeNetwork(int netId) {
-        enforceChangePermission();
+    public boolean removeNetwork(int netId, String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return false;
+        }
         mLog.info("removeNetwork uid=%").c(Binder.getCallingUid()).flush();
         // TODO Add private logging for netId b/33807876
         if (mWifiStateMachineChannel != null) {
@@ -1695,8 +1731,10 @@
      * @return {@code true} if the operation succeeded
      */
     @Override
-    public boolean enableNetwork(int netId, boolean disableOthers) {
-        enforceChangePermission();
+    public boolean enableNetwork(int netId, boolean disableOthers, String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return false;
+        }
         // TODO b/33807876 Log netId
         mLog.info("enableNetwork uid=% disableOthers=%")
                 .c(Binder.getCallingUid())
@@ -1718,8 +1756,10 @@
      * @return {@code true} if the operation succeeded
      */
     @Override
-    public boolean disableNetwork(int netId) {
-        enforceChangePermission();
+    public boolean disableNetwork(int netId, String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return false;
+        }
         // TODO b/33807876 Log netId
         mLog.info("disableNetwork uid=%").c(Binder.getCallingUid()).flush();
 
@@ -1777,8 +1817,11 @@
      * @return true on success or false on failure
      */
     @Override
-    public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
-        enforceChangePermission();
+    public boolean addOrUpdatePasspointConfiguration(
+            PasspointConfiguration config, String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return false;
+        }
         mLog.info("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1795,8 +1838,10 @@
      * @return true on success or false on failure
      */
     @Override
-    public boolean removePasspointConfiguration(String fqdn) {
-        enforceChangePermission();
+    public boolean removePasspointConfiguration(String fqdn, String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return false;
+        }
         mLog.info("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1868,8 +1913,10 @@
      * TODO: deprecate this
      */
     @Override
-    public boolean saveConfiguration() {
-        enforceChangePermission();
+    public boolean saveConfiguration(String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return false;
+        }
         mLog.info("saveConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
@@ -1882,16 +1929,11 @@
     /**
      * Set the country code
      * @param countryCode ISO 3166 country code.
-     * @param persist {@code true} if the setting should be remembered.
      *
-     * The persist behavior exists so that wifi can fall back to the last
-     * persisted country code on a restart, when the locale information is
-     * not available from telephony.
      */
     @Override
-    public void setCountryCode(String countryCode, boolean persist) {
-        Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
-                " with persist set to " + persist);
+    public void setCountryCode(String countryCode) {
+        Slog.i(TAG, "WifiService trying to set country code to " + countryCode);
         enforceConnectivityInternalPermission();
         mLog.info("setCountryCode uid=%").c(Binder.getCallingUid()).flush();
         final long token = Binder.clearCallingIdentity();
@@ -2070,9 +2112,13 @@
      * an AsyncChannel communication with WifiService
      */
     @Override
-    public Messenger getWifiServiceMessenger() {
+    public Messenger getWifiServiceMessenger(String packageName) throws RemoteException {
         enforceAccessPermission();
-        enforceChangePermission();
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            // We don't have a good way of creating a fake Messenger, and returning null would
+            // immediately break callers.
+            throw new RemoteException("Could not create wifi service messenger");
+        }
         mLog.info("getWifiServiceMessenger uid=%").c(Binder.getCallingUid()).flush();
         return new Messenger(mClientHandler);
     }
@@ -2081,9 +2127,11 @@
      * Disable an ephemeral network, i.e. network that is created thru a WiFi Scorer
      */
     @Override
-    public void disableEphemeralNetwork(String SSID) {
+    public void disableEphemeralNetwork(String SSID, String packageName) {
         enforceAccessPermission();
-        enforceChangePermission();
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return;
+        }
         mLog.info("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.disableEphemeralNetwork(SSID);
     }
@@ -2377,6 +2425,7 @@
     @Override
     public void enableVerboseLogging(int verbose) {
         enforceAccessPermission();
+        enforceNetworkSettingsPermission();
         mLog.info("enableVerboseLogging uid=% verbose=%")
                 .c(Binder.getCallingUid())
                 .c(verbose).flush();
@@ -2436,8 +2485,10 @@
     }
 
     @Override
-    public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
-        enforceChangePermission();
+    public boolean setEnableAutoJoinWhenAssociated(boolean enabled, String packageName) {
+        if (enforceChangePermission(packageName) == MODE_IGNORED) {
+            return false;
+        }
         mLog.info("setEnableAutoJoinWhenAssociated uid=% enabled=%")
                 .c(Binder.getCallingUid())
                 .c(enabled).flush();
@@ -2466,7 +2517,7 @@
     }
 
     @Override
-    public void factoryReset() {
+    public void factoryReset(String packageName) {
         enforceConnectivityInternalPermission();
         mLog.info("factoryReset uid=%").c(Binder.getCallingUid()).flush();
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
@@ -2492,9 +2543,9 @@
                         Binder.getCallingUid(), mWifiStateMachineChannel);
                 if (networks != null) {
                     for (WifiConfiguration config : networks) {
-                        removeNetwork(config.networkId);
+                        removeNetwork(config.networkId, packageName);
                     }
-                    saveConfiguration();
+                    saveConfiguration(packageName);
                 }
             }
         }
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index 8e13313..2c8c0b7 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -982,6 +982,7 @@
         mNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
         mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
         mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
         mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
         mNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(1024 * 1024);
         mNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(1024 * 1024);
@@ -1339,7 +1340,7 @@
             return false;
         }
         if (!mWifiConfigManager.enableNetwork(netId, true, uid)
-                || !mWifiConfigManager.checkAndUpdateLastConnectUid(netId, uid)) {
+                || !mWifiConfigManager.updateLastConnectUid(netId, uid)) {
             logi("connectToUserSelectNetwork Allowing uid " + uid
                     + " with insufficient permissions to connect=" + netId);
         } else {
@@ -2925,23 +2926,6 @@
         mWifiApState.set(wifiApState);
 
         if (mVerboseLoggingEnabled) log("setWifiApState: " + syncGetWifiApStateByName());
-
-        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState);
-        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
-        if (wifiApState == WifiManager.WIFI_AP_STATE_FAILED) {
-            //only set reason number when softAP start failed
-            intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
-        }
-
-        if (ifaceName == null) {
-            loge("Updating wifiApState with a null iface name");
-        }
-        intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, ifaceName);
-        intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mode);
-
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     private void setScanResults() {
@@ -5342,8 +5326,10 @@
                     }
                     break;
                 case WifiManager.START_WPS:
+                    mWifiMetrics.incrementWpsAttemptCount();
                     WpsInfo wpsInfo = (WpsInfo) message.obj;
                     if (wpsInfo == null) {
+                        mWifiMetrics.incrementWpsStartFailureCount();
                         loge("Cannot start WPS with null WpsInfo object");
                         replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
                         break;
@@ -5389,6 +5375,7 @@
                         replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult);
                         transitionTo(mWpsRunningState);
                     } else {
+                        mWifiMetrics.incrementWpsStartFailureCount();
                         loge("Failed to start WPS with config " + wpsInfo.toString());
                         replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
                     }
@@ -6814,8 +6801,10 @@
                     int netId = loadResult.second;
                     if (success) {
                         message.arg1 = netId;
+                        mWifiMetrics.incrementWpsSuccessCount();
                         replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED);
                     } else {
+                        mWifiMetrics.incrementWpsSupplicantFailureCount();
                         replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
                                 WifiManager.ERROR);
                     }
@@ -6825,6 +6814,7 @@
                     transitionTo(mDisconnectedState);
                     break;
                 case WifiMonitor.WPS_OVERLAP_EVENT:
+                    mWifiMetrics.incrementWpsOverlapFailureCount();
                     replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
                             WifiManager.WPS_OVERLAP_ERROR);
                     mSourceMessage.recycle();
@@ -6834,6 +6824,7 @@
                 case WifiMonitor.WPS_FAIL_EVENT:
                     // Arg1 has the reason for the failure
                     if ((message.arg1 != WifiManager.ERROR) || (message.arg2 != 0)) {
+                        mWifiMetrics.incrementWpsOtherConnectionFailureCount();
                         replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, message.arg1);
                         mSourceMessage.recycle();
                         mSourceMessage = null;
@@ -6845,6 +6836,7 @@
                     }
                     break;
                 case WifiMonitor.WPS_TIMEOUT_EVENT:
+                    mWifiMetrics.incrementWpsTimeoutFailureCount();
                     replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
                             WifiManager.WPS_TIMED_OUT);
                     mSourceMessage.recycle();
@@ -6855,6 +6847,7 @@
                     replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.IN_PROGRESS);
                     break;
                 case WifiManager.CANCEL_WPS:
+                    mWifiMetrics.incrementWpsCancellationCount();
                     if (mWifiNative.cancelWps()) {
                         replyToMessage(message, WifiManager.CANCEL_WPS_SUCCEDED);
                     } else {
@@ -6984,10 +6977,7 @@
             if (apInterface == null) {
                 setWifiApState(WIFI_AP_STATE_FAILED,
                         WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
-                /**
-                 * Transition to InitialState to reset the
-                 * driver/HAL back to the initial state.
-                 */
+                // Transition to InitialState to reset the driver/HAL back to the initial state.
                 transitionTo(mInitialState);
                 return;
             }
@@ -6995,16 +6985,20 @@
             try {
                 mIfaceName = apInterface.getInterfaceName();
             } catch (RemoteException e) {
-                // Failed to get the interface name. The name will not be available for
-                // the enabled broadcast, but since we had an error getting the name, we most likely
-                // won't be able to fully start softap mode.
+                // Failed to get the interface name. This is not a good sign and we should report
+                // a failure and switch back to the initial state to reset the driver and HAL.
+                setWifiApState(WIFI_AP_STATE_FAILED,
+                        WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
+                transitionTo(mInitialState);
+                return;
             }
 
             checkAndSetConnectivityInstance();
             mSoftApManager = mWifiInjector.makeSoftApManager(mNwService,
                                                              new SoftApListener(),
                                                              apInterface,
-                                                             config.getWifiConfiguration());
+                                                             mIfaceName,
+                                                             config);
             mSoftApManager.start();
             mWifiStateTracker.updateState(WifiStateTracker.SOFT_AP);
         }
diff --git a/com/android/server/wifi/WifiStateMachinePrime.java b/com/android/server/wifi/WifiStateMachinePrime.java
index cd1948f..c49b645 100644
--- a/com/android/server/wifi/WifiStateMachinePrime.java
+++ b/com/android/server/wifi/WifiStateMachinePrime.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.net.wifi.IApInterface;
 import android.net.wifi.IWificond;
 import android.net.wifi.WifiConfiguration;
@@ -50,7 +51,7 @@
 
     private IWificond mWificond;
 
-    private Queue<WifiConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
+    private Queue<SoftApModeConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
 
     /* The base for wifi message types */
     static final int BASE = Protocol.BASE_WIFI;
@@ -106,17 +107,13 @@
     /**
      * Method to enable soft ap for wifi hotspot.
      *
-     * The WifiConfiguration is generally going to be null to indicate that the
-     * currently saved config in WifiApConfigManager should be used.  When the config is
-     * not null, it will be saved in the WifiApConfigManager. This save is performed in the
-     * constructor of SoftApManager.
+     * The supplied SoftApModeConfiguration includes the target softap WifiConfiguration (or null if
+     * the persisted config is to be used) and the target operating mode (ex,
+     * {@link WifiManager.IFACE_IP_MODE_TETHERED} {@link WifiManager.IFACE_IP_MODE_LOCAL_ONLY}).
      *
-     * @param wifiConfig WifiConfiguration for the hostapd softap
+     * @param wifiConfig SoftApModeConfiguration for the hostapd softap
      */
-    public void enterSoftAPMode(WifiConfiguration wifiConfig) {
-        if (wifiConfig == null) {
-            wifiConfig = new WifiConfiguration();
-        }
+    public void enterSoftAPMode(@NonNull SoftApModeConfiguration wifiConfig) {
         mApConfigQueue.offer(wifiConfig);
         changeMode(ModeStateMachine.CMD_START_SOFT_AP_MODE);
     }
@@ -270,6 +267,7 @@
 
         class SoftAPModeState extends State {
             IApInterface mApInterface = null;
+            String mIfaceName = null;
 
             @Override
             public void enter() {
@@ -281,23 +279,18 @@
 
                 // Continue with setup since we are changing modes
                 mApInterface = null;
-                mWificond = mWifiInjector.makeWificond();
-                if (mWificond == null) {
-                    Log.e(TAG, "Failed to get reference to wificond");
-                    writeApConfigDueToStartFailure();
-                    mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
-                    return;
-                }
 
                 try {
+                    mWificond = mWifiInjector.makeWificond();
                     mApInterface = mWificond.createApInterface(
                             mWifiInjector.getWifiNative().getInterfaceName());
-                } catch (RemoteException e1) { }
-
-                if (mApInterface == null) {
-                    Log.e(TAG, "Could not get IApInterface instance from wificond");
-                    writeApConfigDueToStartFailure();
-                    mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
+                    mIfaceName = mApInterface.getInterfaceName();
+                } catch (RemoteException e) {
+                    initializationFailed(
+                            "Could not get IApInterface instance or its name from wificond");
+                    return;
+                } catch (NullPointerException e) {
+                    initializationFailed("wificond failure when initializing softap mode");
                     return;
                 }
                 mModeStateMachine.transitionTo(mSoftAPModeActiveState);
@@ -317,6 +310,8 @@
                         // not in active state, nothing to stop.
                         break;
                     case CMD_START_AP_FAILURE:
+                        // remove the saved config for the start attempt
+                        mApConfigQueue.poll();
                         Log.e(TAG, "Failed to start SoftApMode.  Wait for next mode command.");
                         break;
                     case CMD_AP_STOPPED:
@@ -337,12 +332,13 @@
                 return mApInterface;
             }
 
-            private void writeApConfigDueToStartFailure() {
-                WifiConfiguration config = mApConfigQueue.poll();
-                if (config != null && config.SSID != null) {
-                    // Save valid configs for future calls.
-                    mWifiInjector.getWifiApConfigStore().setApConfiguration(config);
-                }
+            protected String getInterfaceName() {
+                return mIfaceName;
+            }
+
+            private void initializationFailed(String message) {
+                Log.e(TAG, message);
+                mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
             }
         }
 
@@ -410,16 +406,17 @@
             @Override
             public void enter() {
                 Log.d(TAG, "Entering SoftApModeActiveState");
-                WifiConfiguration config = mApConfigQueue.poll();
+                SoftApModeConfiguration softApModeConfig = mApConfigQueue.poll();
+                WifiConfiguration config = softApModeConfig.getWifiConfiguration();
+                // TODO (b/67601382): add checks for valid softap configs
                 if (config != null && config.SSID != null) {
                     Log.d(TAG, "Passing config to SoftApManager! " + config);
                 } else {
                     config = null;
                 }
-
                 this.mActiveModeManager = mWifiInjector.makeSoftApManager(mNMService,
                         new SoftApListener(), ((SoftAPModeState) mSoftAPModeState).getInterface(),
-                        config);
+                        ((SoftAPModeState) mSoftAPModeState).getInterfaceName(), softApModeConfig);
                 mActiveModeManager.start();
             }
 
diff --git a/com/android/server/wifi/WifiVendorHal.java b/com/android/server/wifi/WifiVendorHal.java
index cec36a5..3f39e45 100644
--- a/com/android/server/wifi/WifiVendorHal.java
+++ b/com/android/server/wifi/WifiVendorHal.java
@@ -249,7 +249,8 @@
     public boolean initialize(WifiNative.VendorHalDeathEventHandler handler) {
         synchronized (sLock) {
             mHalDeviceManager.initialize();
-            mHalDeviceManager.registerStatusListener(mHalDeviceManagerStatusCallbacks, mLooper);
+            mHalDeviceManager.registerStatusListener(mHalDeviceManagerStatusCallbacks,
+                    mHalEventHandler);
             mDeathEventHandler = handler;
             return true;
         }
@@ -422,7 +423,6 @@
         mIWifiRttController = null;
         mDriverDescription = null;
         mFirmwareDescription = null;
-        mChannelsForBandSupport = null;
     }
 
     /**
@@ -1453,78 +1453,6 @@
     }
 
     /**
-     * Query the list of valid frequencies for the provided band.
-     * <p>
-     * The result depends on the on the country code that has been set.
-     *
-     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
-     * @return frequencies vector of valid frequencies (MHz), or null for error.
-     * @throws IllegalArgumentException if band is not recognized.
-     */
-    public int[] getChannelsForBand(int band) {
-        enter("%").c(band).flush();
-        class AnswerBox {
-            public int[] value = null;
-        }
-        synchronized (sLock) {
-            try {
-                AnswerBox box = new AnswerBox();
-                int hb = makeWifiBandFromFrameworkBand(band);
-                if (mIWifiStaIface != null) {
-                    mIWifiStaIface.getValidFrequenciesForBand(hb, (status, frequencies) -> {
-                        if (status.code == WifiStatusCode.ERROR_NOT_SUPPORTED) {
-                            mChannelsForBandSupport = false;
-                        }
-                        if (!ok(status)) return;
-                        mChannelsForBandSupport = true;
-                        box.value = intArrayFromArrayList(frequencies);
-                    });
-                } else if (mIWifiApIface != null) {
-                    mIWifiApIface.getValidFrequenciesForBand(hb, (status, frequencies) -> {
-                        if (status.code == WifiStatusCode.ERROR_NOT_SUPPORTED) {
-                            mChannelsForBandSupport = false;
-                        }
-                        if (!ok(status)) return;
-                        mChannelsForBandSupport = true;
-                        box.value = intArrayFromArrayList(frequencies);
-                    });
-                }
-                return box.value;
-            } catch (RemoteException e) {
-                handleRemoteException(e);
-                return null;
-            }
-        }
-    }
-
-    private int[] intArrayFromArrayList(ArrayList<Integer> in) {
-        int[] ans = new int[in.size()];
-        int i = 0;
-        for (Integer e : in) ans[i++] = e;
-        return ans;
-    }
-
-    /**
-     * This holder is null until we know whether or not there is frequency-for-band support.
-     * <p>
-     * Set as a side-effect of getChannelsForBand.
-     */
-    @VisibleForTesting
-    Boolean mChannelsForBandSupport = null;
-
-    /**
-     * Indicates whether getChannelsForBand is supported.
-     *
-     * @return true if it is.
-     */
-    public boolean isGetChannelsForBandSupported() {
-        if (mChannelsForBandSupport != null) return mChannelsForBandSupport;
-        getChannelsForBand(WifiBand.BAND_24GHZ);
-        if (mChannelsForBandSupport != null) return mChannelsForBandSupport;
-        return false;
-    }
-
-    /**
      * Get the APF (Android Packet Filter) capabilities of the device
      */
     public ApfCapabilities getApfCapabilities() {
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index df4e785..aa723d6 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -526,4 +526,38 @@
         }
     }
 
+
+    /**
+     * Query the list of valid frequencies for the provided band.
+     * The result depends on the on the country code that has been set.
+     *
+     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+     * The following bands are supported:
+     * WifiScanner.WIFI_BAND_24_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
+     * @return frequencies vector of valid frequencies (MHz), or null for error.
+     * @throws IllegalArgumentException if band is not recognized.
+     */
+    public int [] getChannelsForBand(int band) {
+        if (mWificond == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return null;
+        }
+        try {
+            switch (band) {
+                case WifiScanner.WIFI_BAND_24_GHZ:
+                    return mWificond.getAvailable2gChannels();
+                case WifiScanner.WIFI_BAND_5_GHZ:
+                    return mWificond.getAvailable5gNonDFSChannels();
+                case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
+                    return mWificond.getAvailableDFSChannels();
+                default:
+                    throw new IllegalArgumentException("unsupported band " + band);
+            }
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+        }
+        return null;
+    }
 }
diff --git a/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java b/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
index efeca65..82bc2c4 100644
--- a/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -122,6 +122,7 @@
         sNetworkCapabilitiesFilter
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
         sNetworkCapabilitiesFilter.setNetworkSpecifier(new MatchAllNetworkSpecifier());
diff --git a/com/android/server/wifi/aware/WifiAwareNativeManager.java b/com/android/server/wifi/aware/WifiAwareNativeManager.java
index e855d81..8659a77 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeManager.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -20,6 +20,7 @@
 import android.hardware.wifi.V1_0.IfaceType;
 import android.hardware.wifi.V1_0.WifiStatus;
 import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -41,6 +42,7 @@
 
     private WifiAwareStateManager mWifiAwareStateManager;
     private HalDeviceManager mHalDeviceManager;
+    private Handler mHandler;
     private WifiAwareNativeCallback mWifiAwareNativeCallback;
     private IWifiNanIface mWifiNanIface = null;
     private InterfaceDestroyedListener mInterfaceDestroyedListener =
@@ -56,7 +58,13 @@
         mWifiAwareNativeCallback = wifiAwareNativeCallback;
     }
 
-    public void start() {
+    /**
+     * Initialize the class - intended for late initialization.
+     *
+     * @param handler Handler on which to execute interface available callbacks.
+     */
+    public void start(Handler handler) {
+        mHandler = handler;
         mHalDeviceManager.initialize();
         mHalDeviceManager.registerStatusListener(
                 new HalDeviceManager.ManagerStatusListener() {
@@ -69,7 +77,7 @@
                             // 1. no problem registering duplicates - only one will be called
                             // 2. will be called immediately if available
                             mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                                    IfaceType.NAN, mInterfaceAvailableForRequestListener, null);
+                                    IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
                         } else {
                             awareIsDown();
                         }
@@ -77,7 +85,7 @@
                 }, null);
         if (mHalDeviceManager.isStarted()) {
             mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                    IfaceType.NAN, mInterfaceAvailableForRequestListener, null);
+                    IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
             tryToGetAware();
         }
     }
@@ -104,7 +112,7 @@
                 return;
             }
             IWifiNanIface iface = mHalDeviceManager.createNanIface(mInterfaceDestroyedListener,
-                    null);
+                    mHandler);
             if (iface == null) {
                 if (DBG) Log.d(TAG, "Was not able to obtain an IWifiNanIface");
             } else {
diff --git a/com/android/server/wifi/aware/WifiAwareStateManager.java b/com/android/server/wifi/aware/WifiAwareStateManager.java
index 31bdff8..0efe736 100644
--- a/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -1625,7 +1625,7 @@
                     waitForResponse = endDataPathLocal(mCurrentTransactionId, msg.arg2);
                     break;
                 case COMMAND_TYPE_DELAYED_INITIALIZATION:
-                    mWifiAwareNativeManager.start();
+                    mWifiAwareNativeManager.start(getHandler());
                     waitForResponse = false;
                     break;
                 default:
diff --git a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index fa16253..da3da7c 100644
--- a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -529,7 +529,7 @@
                     synchronized (mLock) {
                         mIWifiP2pIface = null;
                     }
-                }, mP2pStateMachine.getHandler().getLooper());
+                }, mP2pStateMachine.getHandler());
             }
 
             return messenger;
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
index f731c19..fe1829f 100644
--- a/com/android/server/wifi/rtt/RttNative.java
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -74,6 +74,15 @@
         }
     }
 
+    /**
+     * Returns true if Wi-Fi is ready for RTT requests, false otherwise.
+     */
+    public boolean isReady() {
+        synchronized (mLock) {
+            return mIWifiRttController != null;
+        }
+    }
+
     private void updateController() {
         if (VDBG) Log.v(TAG, "updateController: mIWifiRttController=" + mIWifiRttController);
 
@@ -98,6 +107,12 @@
             } else {
                 mIWifiRttController = null;
             }
+
+            if (mIWifiRttController == null) {
+                mRttService.disable();
+            } else {
+                mRttService.enable();
+            }
         }
     }
 
@@ -112,7 +127,7 @@
     public boolean rangeRequest(int cmdId, RangingRequest request) {
         if (VDBG) Log.v(TAG, "rangeRequest: cmdId=" + cmdId + ", request=" + request);
         synchronized (mLock) {
-            if (mIWifiRttController == null) {
+            if (!isReady()) {
                 Log.e(TAG, "rangeRequest: RttController is null");
                 return false;
             }
@@ -149,7 +164,7 @@
     public boolean rangeCancel(int cmdId, ArrayList<byte[]> macAddresses) {
         if (VDBG) Log.v(TAG, "rangeCancel: cmdId=" + cmdId);
         synchronized (mLock) {
-            if (mIWifiRttController == null) {
+            if (!isReady()) {
                 Log.e(TAG, "rangeCancel: RttController is null");
                 return false;
             }
@@ -223,12 +238,12 @@
                     config.mustRequestLci = false;
                     config.mustRequestLcr = false;
                     config.burstDuration = 15;
-                    if (config.channel.centerFreq > 5000) {
+                    config.bw = halChannelBandwidthFromScanResult(scanResult.channelWidth);
+                    if (config.bw == RttBw.BW_80MHZ || config.bw == RttBw.BW_160MHZ) {
                         config.preamble = RttPreamble.VHT;
                     } else {
                         config.preamble = RttPreamble.HT;
                     }
-                    config.bw = halChannelBandwidthFromScanResult(scanResult.channelWidth);
                 } catch (IllegalArgumentException e) {
                     Log.e(TAG, "Invalid configuration: " + e.getMessage());
                     continue;
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
index 35f7615..0790dee 100644
--- a/com/android/server/wifi/rtt/RttService.java
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -66,7 +66,6 @@
                     Context.WIFI_AWARE_SERVICE);
 
             RttNative rttNative = new RttNative(mImpl, halDeviceManager);
-            rttNative.start();
             mImpl.start(handlerThread.getLooper(), awareBinder, rttNative, wifiPermissionsUtil);
         }
     }
diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java
index 1921047..36caab7 100644
--- a/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -16,7 +16,10 @@
 
 package com.android.server.wifi.rtt;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.RttResult;
 import android.hardware.wifi.V1_0.RttStatus;
@@ -29,12 +32,16 @@
 import android.net.wifi.rtt.RangingRequest;
 import android.net.wifi.rtt.RangingResult;
 import android.net.wifi.rtt.RangingResultCallback;
+import android.net.wifi.rtt.WifiRttManager;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.WorkSource;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -63,7 +70,9 @@
 
     private final Context mContext;
     private IWifiAwareManager mAwareBinder;
+    private RttNative mRttNative;
     private WifiPermissionsUtil mWifiPermissionsUtil;
+    private PowerManager mPowerManager;
 
     private RttServiceSynchronized mRttServiceSynchronized;
 
@@ -91,8 +100,32 @@
     public void start(Looper looper, IWifiAwareManager awareBinder, RttNative rttNative,
             WifiPermissionsUtil wifiPermissionsUtil) {
         mAwareBinder = awareBinder;
+        mRttNative = rttNative;
         mWifiPermissionsUtil = wifiPermissionsUtil;
         mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
+
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (VDBG) Log.v(TAG, "BroadcastReceiver: action=" + action);
+
+                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+                    if (mPowerManager.isDeviceIdleMode()) {
+                        disable();
+                    } else {
+                        enable();
+                    }
+                }
+            }
+        }, intentFilter);
+
+        mRttServiceSynchronized.mHandler.post(() -> {
+            rttNative.start();
+        });
     }
 
     /*
@@ -108,15 +141,50 @@
     }
 
     /**
+     * Enable the API: broadcast notification
+     */
+    public void enable() {
+        if (VDBG) Log.v(TAG, "enable");
+        sendRttStateChangedBroadcast(true);
+        mRttServiceSynchronized.mHandler.post(() -> {
+            // queue should be empty at this point (but this call allows validation)
+            mRttServiceSynchronized.executeNextRangingRequestIfPossible(false);
+        });
+    }
+
+    /**
+     * Disable the API:
+     * - Clean-up (fail) pending requests
+     * - Broadcast notification
+     */
+    public void disable() {
+        if (VDBG) Log.v(TAG, "disable");
+        sendRttStateChangedBroadcast(false);
+        mRttServiceSynchronized.mHandler.post(() -> {
+            mRttServiceSynchronized.cleanUpOnDisable();
+        });
+    }
+
+    /**
+     * Binder interface API to indicate whether the API is currently available. This requires an
+     * immediate asynchronous response.
+     */
+    @Override
+    public boolean isAvailable() {
+        return mRttNative.isReady() && !mPowerManager.isDeviceIdleMode();
+    }
+
+    /**
      * Binder interface API to start a ranging operation. Called on binder thread, operations needs
      * to be posted to handler thread.
      */
     @Override
-    public void startRanging(IBinder binder, String callingPackage, RangingRequest request,
-            IRttCallback callback) throws RemoteException {
+    public void startRanging(IBinder binder, String callingPackage, WorkSource workSource,
+            RangingRequest request, IRttCallback callback) throws RemoteException {
         if (VDBG) {
             Log.v(TAG, "startRanging: binder=" + binder + ", callingPackage=" + callingPackage
-                    + ", request=" + request + ", callback=" + callback);
+                    + ", workSource=" + workSource + ", request=" + request + ", callback="
+                    + callback);
         }
         // verify arguments
         if (binder == null) {
@@ -130,12 +198,24 @@
         }
         request.enforceValidity(mAwareBinder != null);
 
+        if (!isAvailable()) {
+            try {
+                callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+            } catch (RemoteException e) {
+                Log.e(TAG, "startRanging: disabled, callback failed -- " + e);
+            }
+            return;
+        }
+
         final int uid = getMockableCallingUid();
 
-        // permission check
+        // permission checks
         enforceAccessPermission();
         enforceChangePermission();
         enforceLocationPermission(callingPackage, uid);
+        if (workSource != null) {
+            enforceLocationHardware();
+        }
 
         // register for binder death
         IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
@@ -145,7 +225,7 @@
                 binder.unlinkToDeath(this, 0);
 
                 mRttServiceSynchronized.mHandler.post(() -> {
-                    mRttServiceSynchronized.cleanUpOnClientDeath(uid);
+                    mRttServiceSynchronized.cleanUpClientRequests(uid, null);
                 });
             }
         };
@@ -156,9 +236,29 @@
             Log.e(TAG, "Error on linkToDeath - " + e);
         }
 
+
         mRttServiceSynchronized.mHandler.post(() -> {
-            mRttServiceSynchronized.queueRangingRequest(uid, binder, dr, callingPackage, request,
-                    callback);
+            WorkSource sourceToUse = workSource;
+            if (workSource == null || workSource.size() == 0 || workSource.get(0) == 0) {
+                sourceToUse = new WorkSource(uid);
+            }
+            mRttServiceSynchronized.queueRangingRequest(uid, sourceToUse, binder, dr,
+                    callingPackage, request, callback);
+        });
+    }
+
+    @Override
+    public void cancelRanging(WorkSource workSource) throws RemoteException {
+        if (VDBG) Log.v(TAG, "cancelRanging: workSource=" + workSource);
+        enforceLocationHardware();
+
+        if (workSource == null || workSource.size() == 0 || workSource.get(0) == 0) {
+            Log.e(TAG, "cancelRanging: invalid work-source -- " + workSource);
+            return;
+        }
+
+        mRttServiceSynchronized.mHandler.post(() -> {
+            mRttServiceSynchronized.cleanUpClientRequests(0, workSource);
         });
     }
 
@@ -186,6 +286,18 @@
                 TAG);
     }
 
+    private void enforceLocationHardware() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE,
+                TAG);
+    }
+
+    private void sendRttStateChangedBroadcast(boolean enabled) {
+        if (VDBG) Log.v(TAG, "sendRttStateChangedBroadcast: enabled=" + enabled);
+        final Intent intent = new Intent(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(
@@ -249,18 +361,65 @@
             mRttNative.rangeCancel(rri.cmdId, macAddresses);
         }
 
-        private void cleanUpOnClientDeath(int uid) {
+        private void cleanUpOnDisable() {
+            if (VDBG) Log.v(TAG, "RttServiceSynchronized.cleanUpOnDisable");
+            for (RttRequestInfo rri : mRttRequestQueue) {
+                try {
+                    if (rri.dispatchedToNative) {
+                        // may not be necessary in some cases (e.g. Wi-Fi disable may already clear
+                        // up active RTT), but in other cases will be needed (doze disabling RTT
+                        // but Wi-Fi still up). Doesn't hurt - worst case will fail.
+                        cancelRanging(rri);
+                    }
+                    rri.callback.onRangingFailure(
+                            RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.startRanging: disabled, callback failed -- "
+                            + e);
+                }
+                rri.binder.unlinkToDeath(rri.dr, 0);
+            }
+            mRttRequestQueue.clear();
+            mRangingTimeoutMessage.cancel();
+        }
+
+        /**
+         * Remove entries related to the specified client and cancel any dispatched to HAL
+         * requests. Expected to provide either the UID or the WorkSource (the other will be 0 or
+         * null respectively).
+         *
+         * A workSource specification will be cleared from the requested workSource and the request
+         * cancelled only if there are no remaining uids in the work-source.
+         */
+        private void cleanUpClientRequests(int uid, WorkSource workSource) {
             if (VDBG) {
                 Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
-                        + ", mRttRequestQueue=" + mRttRequestQueue);
+                        + ", workSource=" + workSource + ", mRttRequestQueue=" + mRttRequestQueue);
             }
+            boolean dispatchedRequestAborted = false;
             ListIterator<RttRequestInfo> it = mRttRequestQueue.listIterator();
             while (it.hasNext()) {
                 RttRequestInfo rri = it.next();
-                if (rri.uid == uid) {
+
+                boolean match = rri.uid == uid; // original UID will never be 0
+                if (rri.workSource != null && workSource != null) {
+                    try {
+                        rri.workSource.remove(workSource);
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "Invalid WorkSource specified in the start or cancel requests: "
+                                + e);
+                    }
+                    if (rri.workSource.size() == 0) {
+                        match = true;
+                    }
+                }
+
+                if (match) {
                     if (!rri.dispatchedToNative) {
                         it.remove();
+                        rri.binder.unlinkToDeath(rri.dr, 0);
                     } else {
+                        dispatchedRequestAborted = true;
                         Log.d(TAG, "Client death - cancelling RTT operation in progress: cmdId="
                                 + rri.cmdId);
                         mRangingTimeoutMessage.cancel();
@@ -271,8 +430,13 @@
 
             if (VDBG) {
                 Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+                        + ", dispatchedRequestAborted=" + dispatchedRequestAborted
                         + ", after cleanup - mRttRequestQueue=" + mRttRequestQueue);
             }
+
+            if (dispatchedRequestAborted) {
+                executeNextRangingRequestIfPossible(true);
+            }
         }
 
         private void timeoutRangingRequest() {
@@ -299,10 +463,12 @@
             executeNextRangingRequestIfPossible(true);
         }
 
-        private void queueRangingRequest(int uid, IBinder binder, IBinder.DeathRecipient dr,
-                String callingPackage, RangingRequest request, IRttCallback callback) {
+        private void queueRangingRequest(int uid, WorkSource workSource, IBinder binder,
+                IBinder.DeathRecipient dr, String callingPackage, RangingRequest request,
+                IRttCallback callback) {
             RttRequestInfo newRequest = new RttRequestInfo();
             newRequest.uid = uid;
+            newRequest.workSource = workSource;
             newRequest.binder = binder;
             newRequest.dr = dr;
             newRequest.callingPackage = callingPackage;
@@ -325,7 +491,8 @@
                     Log.w(TAG, "executeNextRangingRequestIfPossible: pop requested - but empty "
                             + "queue!? Ignoring pop.");
                 } else {
-                    mRttRequestQueue.remove(0);
+                    RttRequestInfo topOfQueueRequest = mRttRequestQueue.remove(0);
+                    topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
                 }
             }
 
@@ -334,6 +501,7 @@
                 return;
             }
 
+            // if top of list is in progress then do nothing
             RttRequestInfo nextRequest = mRttRequestQueue.get(0);
             if (nextRequest.peerHandlesTranslated || nextRequest.dispatchedToNative) {
                 if (VDBG) {
@@ -351,6 +519,19 @@
                 Log.v(TAG, "RttServiceSynchronized.startRanging: nextRequest=" + nextRequest);
             }
 
+            if (!isAvailable()) {
+                Log.d(TAG, "RttServiceSynchronized.startRanging: disabled");
+                try {
+                    nextRequest.callback.onRangingFailure(
+                            RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.startRanging: disabled, callback failed -- "
+                            + e);
+                    executeNextRangingRequestIfPossible(true);
+                    return;
+                }
+            }
+
             if (processAwarePeerHandles(nextRequest)) {
                 if (VDBG) {
                     Log.v(TAG, "RttServiceSynchronized.startRanging: deferring due to PeerHandle "
@@ -506,10 +687,6 @@
                         "RttServiceSynchronized.onRangingResults: callback exception -- " + e);
             }
 
-            // clean-up binder death listener: the callback for results is a onetime event - now
-            // done with the binder.
-            topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
-
             executeNextRangingRequestIfPossible(true);
         }
 
@@ -578,12 +755,14 @@
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("  mNextCommandId: " + mNextCommandId);
             pw.println("  mRttRequestQueue: " + mRttRequestQueue);
+            pw.println("  mRangingTimeoutMessage: " + mRangingTimeoutMessage);
             mRttNative.dump(fd, pw, args);
         }
     }
 
     private static class RttRequestInfo {
         public int uid;
+        public WorkSource workSource;
         public IBinder binder;
         public IBinder.DeathRecipient dr;
         public String callingPackage;
@@ -596,10 +775,11 @@
 
         @Override
         public String toString() {
-            return new StringBuilder("RttRequestInfo: uid=").append(uid).append(", binder=").append(
-                    binder).append(", dr=").append(dr).append(", callingPackage=").append(
-                    callingPackage).append(", request=").append(request.toString()).append(
-                    ", callback=").append(callback).append(", cmdId=").append(cmdId).append(
+            return new StringBuilder("RttRequestInfo: uid=").append(uid).append(
+                    ", workSource=").append(workSource).append(", binder=").append(binder).append(
+                    ", dr=").append(dr).append(", callingPackage=").append(callingPackage).append(
+                    ", request=").append(request.toString()).append(", callback=").append(
+                    callback).append(", cmdId=").append(cmdId).append(
                     ", peerHandlesTranslated=").append(peerHandlesTranslated).toString();
         }
     }
diff --git a/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
index 890f72e..1478a99 100644
--- a/com/android/server/wifi/scanner/HalWifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
@@ -46,7 +46,7 @@
     public HalWifiScannerImpl(Context context, WifiNative wifiNative, WifiMonitor wifiMonitor,
                               Looper looper, Clock clock) {
         mWifiNative = wifiNative;
-        mChannelHelper = new HalChannelHelper(wifiNative);
+        mChannelHelper = new WificondChannelHelper(wifiNative);
         mWificondScannerDelegate =
                 new WificondScannerImpl(context, wifiNative, wifiMonitor, mChannelHelper,
                         looper, clock);
diff --git a/com/android/server/wifi/scanner/NoBandChannelHelper.java b/com/android/server/wifi/scanner/NoBandChannelHelper.java
deleted file mode 100644
index b2eeada..0000000
--- a/com/android/server/wifi/scanner/NoBandChannelHelper.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi.scanner;
-
-import android.net.wifi.WifiScanner;
-import android.util.ArraySet;
-
-import com.android.server.wifi.WifiNative;
-
-import java.util.Set;
-
-/**
- * ChannelHelper that offers channel manipulation utilities when the channels in a band are not
- * known. Operations performed may simplify any band to include all channels.
- */
-public class NoBandChannelHelper extends ChannelHelper {
-
-    /**
-     * These parameters are used to estimate the scan duration.
-     * This is a guess at the number of channels the device supports for use when a ScanSettings
-     * specifies a band instead of a list of channels.
-     */
-    private static final int ALL_BAND_CHANNEL_COUNT_ESTIMATE = 36;
-
-    @Override
-    public boolean settingsContainChannel(WifiScanner.ScanSettings settings, int channel) {
-        if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
-            for (int i = 0; i < settings.channels.length; ++i) {
-                if (settings.channels[i].frequency == channel) {
-                    return true;
-                }
-            }
-            return false;
-        } else {
-            return true;
-        }
-    }
-
-    @Override
-    public WifiScanner.ChannelSpec[] getAvailableScanChannels(int band) {
-        return NO_CHANNELS; // not supported
-    }
-
-    @Override
-    public int estimateScanDuration(WifiScanner.ScanSettings settings) {
-        if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
-            return settings.channels.length * SCAN_PERIOD_PER_CHANNEL_MS;
-        } else {
-            return ALL_BAND_CHANNEL_COUNT_ESTIMATE * SCAN_PERIOD_PER_CHANNEL_MS;
-        }
-    }
-
-    /**
-     * ChannelCollection that merges channels without knowing which channels are in each band. In
-     * order to do this if any band is added or the maxChannels is exceeded then all channels will
-     * be included.
-     */
-    public class NoBandChannelCollection extends ChannelCollection {
-        private final ArraySet<Integer> mChannels = new ArraySet<Integer>();
-        private boolean mAllChannels = false;
-
-        @Override
-        public void addChannel(int frequency) {
-            mChannels.add(frequency);
-        }
-
-        @Override
-        public void addBand(int band) {
-            if (band != WifiScanner.WIFI_BAND_UNSPECIFIED) {
-                mAllChannels = true;
-            }
-        }
-
-        @Override
-        public boolean containsChannel(int channel) {
-            return mAllChannels || mChannels.contains(channel);
-        }
-
-        @Override
-        public boolean containsBand(int band) {
-            if (band != WifiScanner.WIFI_BAND_UNSPECIFIED) {
-                return mAllChannels;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean partiallyContainsBand(int band) {
-            // We don't need to partially collapse settings in wificond scanner because we
-            // don't have any limitation on the number of channels that can be scanned. We also
-            // don't currently keep track of bands very well in NoBandChannelHelper.
-            return false;
-        }
-
-        @Override
-        public boolean isEmpty() {
-            return !mAllChannels && mChannels.isEmpty();
-        }
-
-        @Override
-        public boolean isAllChannels() {
-            return mAllChannels;
-        }
-
-        @Override
-        public void clear() {
-            mAllChannels = false;
-            mChannels.clear();
-        }
-
-        @Override
-        public Set<Integer> getMissingChannelsFromBand(int band) {
-            // We don't need to partially collapse settings in wificond scanner because we
-            // don't have any limitation on the number of channels that can be scanned. We also
-            // don't currently keep track of bands very well in NoBandChannelHelper.
-            return new ArraySet<Integer>();
-        }
-
-        @Override
-        public Set<Integer> getContainingChannelsFromBand(int band) {
-            // We don't need to partially collapse settings in wificond scanner because we
-            // don't have any limitation on the number of channels that can be scanned. We also
-            // don't currently keep track of bands very well in NoBandChannelHelper.
-            return new ArraySet<Integer>();
-        }
-
-        @Override
-        public Set<Integer> getChannelSet() {
-            if (!isEmpty() && !mAllChannels) {
-                return mChannels;
-            } else {
-                return new ArraySet<>();
-            }
-        }
-
-        @Override
-        public void fillBucketSettings(WifiNative.BucketSettings bucketSettings, int maxChannels) {
-            if (mAllChannels || mChannels.size() > maxChannels) {
-                bucketSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
-                bucketSettings.num_channels = 0;
-                bucketSettings.channels = null;
-            } else {
-                bucketSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
-                bucketSettings.num_channels = mChannels.size();
-                bucketSettings.channels = new WifiNative.ChannelSettings[mChannels.size()];
-                for (int i = 0; i < mChannels.size(); ++i) {
-                    WifiNative.ChannelSettings channelSettings = new WifiNative.ChannelSettings();
-                    channelSettings.frequency = mChannels.valueAt(i);
-                    bucketSettings.channels[i] = channelSettings;
-                }
-            }
-        }
-
-        @Override
-        public Set<Integer> getScanFreqs() {
-            if (mAllChannels) {
-                return null;
-            } else {
-                return new ArraySet<Integer>(mChannels);
-            }
-        }
-    }
-
-    @Override
-    public ChannelCollection createChannelCollection() {
-        return new NoBandChannelCollection();
-    }
-}
diff --git a/com/android/server/wifi/scanner/WifiScannerImpl.java b/com/android/server/wifi/scanner/WifiScannerImpl.java
index dacb007..75b24d8 100644
--- a/com/android/server/wifi/scanner/WifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/WifiScannerImpl.java
@@ -53,8 +53,8 @@
                 if (wifiNative.getBgScanCapabilities(new WifiNative.ScanCapabilities())) {
                     return new HalWifiScannerImpl(context, wifiNative, wifiMonitor, looper, clock);
                 } else {
-                    return new WificondScannerImpl(context, wifiNative, wifiMonitor, looper,
-                            clock);
+                    return new WificondScannerImpl(context, wifiNative, wifiMonitor,
+                            new WificondChannelHelper(wifiNative), looper, clock);
                 }
             }
         };
diff --git a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index 02462f6..ed1bedf 100644
--- a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -1336,7 +1336,6 @@
      *   -Started State
      *     -Hw Pno Scan state
      *       -Single Scan state
-     *     -Sw Pno Scan state
      *
      * These are the main state transitions:
      * 1. Start at |Default State|
@@ -1348,11 +1347,6 @@
      *        contains IE (information elements). If yes, send the results to the client, else
      *        switch to |Single Scan state| and send the result to the client when the scan result
      *        is obtained.
-     *   b.1. Switch to |Sw Pno Scan state| when the device does not supports HW PNO
-     *        (This is for older devices which do not support HW PNO and for connected PNO on
-     *         devices which support wificond based PNO)
-     *   b.2. In |Sw Pno Scan state| send the result to the client when the background scan result
-     *        is obtained
      *
      * Note: PNO scans only work for a single client today. We don't have support in HW to support
      * multiple requests at the same time, so will need non-trivial changes to support (if at all
@@ -1363,7 +1357,6 @@
         private final DefaultState mDefaultState = new DefaultState();
         private final StartedState mStartedState = new StartedState();
         private final HwPnoScanState mHwPnoScanState = new HwPnoScanState();
-        private final SwPnoScanState mSwPnoScanState = new SwPnoScanState();
         private final SingleScanState mSingleScanState = new SingleScanState();
         private InternalClientInfo mInternalClientInfo;
 
@@ -1381,7 +1374,6 @@
                 addState(mStartedState, mDefaultState);
                     addState(mHwPnoScanState, mStartedState);
                         addState(mSingleScanState, mHwPnoScanState);
-                    addState(mSwPnoScanState, mStartedState);
             // CHECKSTYLE:ON IndentationCheck
 
             setInitialState(mDefaultState);
@@ -1461,12 +1453,11 @@
                         pnoParams.setDefusable(true);
                         PnoSettings pnoSettings =
                                 pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY);
-                        // This message is handled after the transition to SwPnoScan/HwPnoScan state
-                        deferMessage(msg);
                         if (mScannerImpl.isHwPnoSupported(pnoSettings.isConnected)) {
+                            deferMessage(msg);
                             transitionTo(mHwPnoScanState);
                         } else {
-                            transitionTo(mSwPnoScanState);
+                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "not supported");
                         }
                         break;
                     case WifiScanner.CMD_STOP_PNO_SCAN:
@@ -1577,68 +1568,6 @@
             }
         }
 
-        class SwPnoScanState extends State {
-            private final ArrayList<ScanResult> mSwPnoFullScanResults = new ArrayList<>();
-
-            @Override
-            public void enter() {
-                if (DBG) localLog("SwPnoScanState");
-                mSwPnoFullScanResults.clear();
-            }
-
-            @Override
-            public void exit() {
-                removeInternalClient();
-            }
-
-            @Override
-            public boolean processMessage(Message msg) {
-                ClientInfo ci = mClients.get(msg.replyTo);
-                switch (msg.what) {
-                    case WifiScanner.CMD_START_PNO_SCAN:
-                        Bundle pnoParams = (Bundle) msg.obj;
-                        if (pnoParams == null) {
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
-                            return HANDLED;
-                        }
-                        pnoParams.setDefusable(true);
-                        PnoSettings pnoSettings =
-                                pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY);
-                        ScanSettings scanSettings =
-                                pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY);
-                        if (addSwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) {
-                            replySucceeded(msg);
-                        } else {
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
-                            transitionTo(mStartedState);
-                        }
-                        break;
-                    case WifiScanner.CMD_STOP_PNO_SCAN:
-                        removeSwPnoScanRequest(ci, msg.arg2);
-                        transitionTo(mStartedState);
-                        break;
-                    case WifiScanner.CMD_FULL_SCAN_RESULT:
-                        // Aggregate full scan results until we get the |CMD_SCAN_RESULT| message
-                        mSwPnoFullScanResults.add((ScanResult) msg.obj);
-                        break;
-                    case WifiScanner.CMD_SCAN_RESULT:
-                        ScanResult[] scanResults = mSwPnoFullScanResults.toArray(
-                                new ScanResult[mSwPnoFullScanResults.size()]);
-                        reportPnoNetworkFound(scanResults);
-                        mSwPnoFullScanResults.clear();
-                        break;
-                    case WifiScanner.CMD_OP_FAILED:
-                        sendPnoScanFailedToAllAndClear(
-                                WifiScanner.REASON_UNSPECIFIED, "background scan failed");
-                        transitionTo(mStartedState);
-                        break;
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-        }
-
         private WifiNative.PnoSettings convertSettingsToPnoNative(ScanSettings scanSettings,
                                                                   PnoSettings pnoSettings) {
             WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings();
@@ -1731,34 +1660,6 @@
             }
         }
 
-        private boolean addSwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings,
-                PnoSettings pnoSettings) {
-            if (ci == null) {
-                Log.d(TAG, "Failing scan request ClientInfo not found " + handler);
-                return false;
-            }
-            if (!mActivePnoScans.isEmpty()) {
-                loge("Failing scan request because there is already an active scan");
-                return false;
-            }
-            logScanRequest("addSwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings);
-            addPnoScanRequest(ci, handler, scanSettings, pnoSettings);
-            // HW PNO is not supported, we need to revert to normal background scans and
-            // report events after each scan and we need full scan results to get the IE information
-            scanSettings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
-                    | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
-            addBackgroundScanRequest(scanSettings);
-            return true;
-        }
-
-        private void removeSwPnoScanRequest(ClientInfo ci, int handler) {
-            if (ci != null) {
-                Pair<PnoSettings, ScanSettings> settings = removePnoScanRequest(ci, handler);
-                logScanRequest("removeSwPnoScanRequest", ci, handler, null,
-                        settings.second, settings.first);
-            }
-        }
-
         private void reportPnoNetworkFound(ScanResult[] results) {
             WifiScanner.ParcelableScanResults parcelableScanResults =
                     new WifiScanner.ParcelableScanResults(results);
@@ -1781,15 +1682,6 @@
             mActivePnoScans.clear();
         }
 
-        private void addBackgroundScanRequest(ScanSettings settings) {
-            if (DBG) localLog("Starting background scan");
-            if (mInternalClientInfo != null) {
-                mInternalClientInfo.sendRequestToClientHandler(
-                        WifiScanner.CMD_START_BACKGROUND_SCAN, settings,
-                        WifiStateMachine.WIFI_WORK_SOURCE);
-            }
-        }
-
         private void addSingleScanRequest(ScanSettings settings) {
             if (DBG) localLog("Starting single scan");
             if (mInternalClientInfo != null) {
diff --git a/com/android/server/wifi/scanner/HalChannelHelper.java b/com/android/server/wifi/scanner/WificondChannelHelper.java
similarity index 79%
rename from com/android/server/wifi/scanner/HalChannelHelper.java
rename to com/android/server/wifi/scanner/WificondChannelHelper.java
index e8b646d..7cfeb72 100644
--- a/com/android/server/wifi/scanner/HalChannelHelper.java
+++ b/com/android/server/wifi/scanner/WificondChannelHelper.java
@@ -22,15 +22,15 @@
 import com.android.server.wifi.WifiNative;
 
 /**
- * KnownBandsChannelHelper that uses band to channel mappings retrieved from the HAL.
- * Also supporting updating the channel list from the HAL on demand.
+ * KnownBandsChannelHelper that uses band to channel mappings retrieved from wificond.
+ * Also supporting updating the channel list from the wificond on demand.
  */
-public class HalChannelHelper extends KnownBandsChannelHelper {
-    private static final String TAG = "HalChannelHelper";
+public class WificondChannelHelper extends KnownBandsChannelHelper {
+    private static final String TAG = "WificondChannelHelper";
 
     private final WifiNative mWifiNative;
 
-    public HalChannelHelper(WifiNative wifiNative) {
+    public WificondChannelHelper(WifiNative wifiNative) {
         mWifiNative = wifiNative;
         final int[] emptyFreqList = new int[0];
         setBandChannels(emptyFreqList, emptyFreqList, emptyFreqList);
@@ -39,11 +39,13 @@
 
     @Override
     public void updateChannels() {
-        int[] channels24G = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
+        int[] channels24G =
+                mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
         if (channels24G == null) Log.e(TAG, "Failed to get channels for 2.4GHz band");
         int[] channels5G = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
         if (channels5G == null) Log.e(TAG, "Failed to get channels for 5GHz band");
-        int[] channelsDfs = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
+        int[] channelsDfs =
+                mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
         if (channelsDfs == null) Log.e(TAG, "Failed to get channels for 5GHz DFS only band");
         if (channels24G == null || channels5G == null || channelsDfs == null) {
             Log.e(TAG, "Failed to get all channels for band, not updating band channel lists");
diff --git a/com/android/server/wifi/scanner/WificondScannerImpl.java b/com/android/server/wifi/scanner/WificondScannerImpl.java
index 10fc8e3..72a7a27 100644
--- a/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -128,12 +128,6 @@
                 WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
     }
 
-    public WificondScannerImpl(Context context, WifiNative wifiNative,
-                                     WifiMonitor wifiMonitor, Looper looper, Clock clock) {
-        // TODO get channel information from wificond.
-        this(context, wifiNative, wifiMonitor, new NoBandChannelHelper(), looper, clock);
-    }
-
     @Override
     public void cleanup() {
         synchronized (mSettingsLock) {
diff --git a/com/android/server/wifi/util/ApConfigUtil.java b/com/android/server/wifi/util/ApConfigUtil.java
index 7bbbc03..b1a4948 100644
--- a/com/android/server/wifi/util/ApConfigUtil.java
+++ b/com/android/server/wifi/util/ApConfigUtil.java
@@ -133,15 +133,9 @@
                     config.apBand, allowed2GChannels,
                     wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ));
             if (config.apChannel == -1) {
-                if (wifiNative.isGetChannelsForBandSupported()) {
-                    /* We're not able to get channel when it is supported by HAL. */
-                    Log.e(TAG, "Failed to get available channel.");
-                    return ERROR_NO_CHANNEL;
-                }
-
-                /* Use the default for HAL without get channel support. */
-                config.apBand = DEFAULT_AP_BAND;
-                config.apChannel = DEFAULT_AP_CHANNEL;
+                /* We're not able to get channel from wificond. */
+                Log.e(TAG, "Failed to get available channel.");
+                return ERROR_NO_CHANNEL;
             }
         }
 
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index e873d32..98db80e 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -21,7 +21,6 @@
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.os.Build.VERSION_CODES.O_MR1;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
@@ -1101,52 +1100,54 @@
                     + " from " + fromToken + " to " + this);
 
             final long origId = Binder.clearCallingIdentity();
+            try {
+                // Transfer the starting window over to the new token.
+                startingData = fromToken.startingData;
+                startingSurface = fromToken.startingSurface;
+                startingDisplayed = fromToken.startingDisplayed;
+                fromToken.startingDisplayed = false;
+                startingWindow = tStartingWindow;
+                reportedVisible = fromToken.reportedVisible;
+                fromToken.startingData = null;
+                fromToken.startingSurface = null;
+                fromToken.startingWindow = null;
+                fromToken.startingMoved = true;
+                tStartingWindow.mToken = this;
+                tStartingWindow.mAppToken = this;
 
-            // Transfer the starting window over to the new token.
-            startingData = fromToken.startingData;
-            startingSurface = fromToken.startingSurface;
-            startingDisplayed = fromToken.startingDisplayed;
-            fromToken.startingDisplayed = false;
-            startingWindow = tStartingWindow;
-            reportedVisible = fromToken.reportedVisible;
-            fromToken.startingData = null;
-            fromToken.startingSurface = null;
-            fromToken.startingWindow = null;
-            fromToken.startingMoved = true;
-            tStartingWindow.mToken = this;
-            tStartingWindow.mAppToken = this;
+                if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
+                        "Removing starting " + tStartingWindow + " from " + fromToken);
+                fromToken.removeChild(tStartingWindow);
+                fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
+                fromToken.mHiddenSetFromTransferredStartingWindow = false;
+                addWindow(tStartingWindow);
 
-            if (DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
-                    "Removing starting " + tStartingWindow + " from " + fromToken);
-            fromToken.removeChild(tStartingWindow);
-            fromToken.postWindowRemoveStartingWindowCleanup(tStartingWindow);
-            fromToken.mHiddenSetFromTransferredStartingWindow = false;
-            addWindow(tStartingWindow);
+                // Propagate other interesting state between the tokens. If the old token is displayed,
+                // we should immediately force the new one to be displayed. If it is animating, we need
+                // to move that animation to the new one.
+                if (fromToken.allDrawn) {
+                    allDrawn = true;
+                    deferClearAllDrawn = fromToken.deferClearAllDrawn;
+                }
+                if (fromToken.firstWindowDrawn) {
+                    firstWindowDrawn = true;
+                }
+                if (!fromToken.hidden) {
+                    hidden = false;
+                    hiddenRequested = false;
+                    mHiddenSetFromTransferredStartingWindow = true;
+                }
+                setClientHidden(fromToken.mClientHidden);
+                fromToken.mAppAnimator.transferCurrentAnimation(
+                        mAppAnimator, tStartingWindow.mWinAnimator);
 
-            // Propagate other interesting state between the tokens. If the old token is displayed,
-            // we should immediately force the new one to be displayed. If it is animating, we need
-            // to move that animation to the new one.
-            if (fromToken.allDrawn) {
-                allDrawn = true;
-                deferClearAllDrawn = fromToken.deferClearAllDrawn;
+                mService.updateFocusedWindowLocked(
+                        UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
+                getDisplayContent().setLayoutNeeded();
+                mService.mWindowPlacerLocked.performSurfacePlacement();
+            } finally {
+                Binder.restoreCallingIdentity(origId);
             }
-            if (fromToken.firstWindowDrawn) {
-                firstWindowDrawn = true;
-            }
-            if (!fromToken.hidden) {
-                hidden = false;
-                hiddenRequested = false;
-                mHiddenSetFromTransferredStartingWindow = true;
-            }
-            setClientHidden(fromToken.mClientHidden);
-            fromToken.mAppAnimator.transferCurrentAnimation(
-                    mAppAnimator, tStartingWindow.mWinAnimator);
-
-            mService.updateFocusedWindowLocked(
-                    UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/);
-            getDisplayContent().setLayoutNeeded();
-            mService.mWindowPlacerLocked.performSurfacePlacement();
-            Binder.restoreCallingIdentity(origId);
             return true;
         } else if (fromToken.startingData != null) {
             // The previous app was getting ready to show a
@@ -1201,15 +1202,6 @@
      */
     @Override
     int getOrientation(int candidate) {
-        // We do not allow non-fullscreen apps to influence orientation starting in O-MR1. While we
-        // do throw an exception in {@link Activity#onCreate} and
-        // {@link Activity#setRequestedOrientation}, we also ignore the orientation here so that
-        // other calculations aren't affected.
-        if (!fillsParent() && mTargetSdk >= O_MR1) {
-            // Can't specify orientation if app doesn't fill parent.
-            return SCREEN_ORIENTATION_UNSET;
-        }
-
         if (candidate == SCREEN_ORIENTATION_BEHIND) {
             // Allow app to specify orientation regardless of its visibility state if the current
             // candidate want us to use orientation behind. I.e. the visible app on-top of this one
diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java
index 401547e..8fb2be8 100644
--- a/com/android/server/wm/DimLayer.java
+++ b/com/android/server/wm/DimLayer.java
@@ -119,7 +119,7 @@
         } catch (Exception e) {
             Slog.e(TAG_WM, "Exception creating Dim surface", e);
         } finally {
-            service.closeSurfaceTransaction();
+            service.closeSurfaceTransaction("DimLayer.constructSurface");
         }
     }
 
@@ -235,7 +235,7 @@
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failure setting size", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("DimLayer.setBounds");
             }
         }
     }
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 4c6ab3f..4d839d0 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -104,6 +105,7 @@
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
 import static com.android.server.wm.proto.DisplayProto.ABOVE_APP_WINDOWS;
 import static com.android.server.wm.proto.DisplayProto.BELOW_APP_WINDOWS;
+import static com.android.server.wm.proto.DisplayProto.DISPLAY_FRAMES;
 import static com.android.server.wm.proto.DisplayProto.DISPLAY_INFO;
 import static com.android.server.wm.proto.DisplayProto.DOCKED_STACK_DIVIDER_CONTROLLER;
 import static com.android.server.wm.proto.DisplayProto.DPI;
@@ -147,6 +149,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.view.IInputMethodClient;
+import android.view.DisplayFrames;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -222,6 +225,8 @@
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
     private final Display mDisplay;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    DisplayFrames mDisplayFrames;
+
     /**
      * For default display it contains real metrics, empty for others.
      * @see WindowManagerService#createWatermarkInTransaction()
@@ -285,7 +290,6 @@
     private boolean mLastWallpaperVisible = false;
 
     private Rect mBaseDisplayRect = new Rect();
-    private Rect mContentRect = new Rect();
 
     // Accessed directly by all users.
     private boolean mLayoutNeeded;
@@ -545,7 +549,7 @@
                 w.mLayoutNeeded = false;
                 w.prelayout();
                 final boolean firstLayout = !w.isLaidOut();
-                mService.mPolicy.layoutWindowLw(w, null);
+                mService.mPolicy.layoutWindowLw(w, null, mDisplayFrames);
                 w.mLayoutSeq = mService.mLayoutSeq;
 
                 // If this is the first layout, we need to initialize the last inset values as
@@ -586,7 +590,7 @@
                 }
                 w.mLayoutNeeded = false;
                 w.prelayout();
-                mService.mPolicy.layoutWindowLw(w, w.getParentWindow());
+                mService.mPolicy.layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
                 w.mLayoutSeq = mService.mLayoutSeq;
                 if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
                         + " mContainingFrame=" + w.mContainingFrame
@@ -758,6 +762,7 @@
         display.getMetrics(mDisplayMetrics);
         isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
         mService = service;
+        mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo);
         initializeDisplayBaseInfo();
         mDividerControllerLocked = new DockedStackDividerController(service, this);
         mPinnedStackControllerLocked = new PinnedStackController(service, this);
@@ -1083,7 +1088,7 @@
             mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
         } finally {
             if (!inTransaction) {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("setRotationUnchecked");
                 if (SHOW_LIGHT_TRANSACTIONS) {
                     Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
                 }
@@ -1129,6 +1134,13 @@
         return true;
     }
 
+    void configureDisplayPolicy() {
+        mService.mPolicy.setInitialDisplaySize(getDisplay(),
+                mBaseDisplayWidth, mBaseDisplayHeight, mBaseDisplayDensity);
+
+        mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo);
+    }
+
     /**
      * Update {@link #mDisplayInfo} and other internal variables when display is rotated or config
      * changed.
@@ -1452,17 +1464,17 @@
     /**
      * @return The primary split-screen stack, but only if it is visible, and {@code null} otherwise.
      */
-    TaskStack getSplitScreenPrimaryStackStack() {
-        TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStackStack();
+    TaskStack getSplitScreenPrimaryStack() {
+        TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStack();
         return (stack != null && stack.isVisible()) ? stack : null;
     }
 
     /**
-     * Like {@link #getSplitScreenPrimaryStackStack}, but also returns the stack if it's currently
+     * Like {@link #getSplitScreenPrimaryStack}, but also returns the stack if it's currently
      * not visible.
      */
-    TaskStack getSplitScreenPrimaryStackStackIgnoringVisibility() {
-        return mTaskStackContainers.getSplitScreenPrimaryStackStack();
+    TaskStack getSplitScreenPrimaryStackIgnoringVisibility() {
+        return mTaskStackContainers.getSplitScreenPrimaryStack();
     }
 
     TaskStack getPinnedStack() {
@@ -1477,7 +1489,7 @@
      * Returns the topmost stack on the display that is compatible with the input windowing mode.
      * Null is no compatible stack on the display.
      */
-    TaskStack getStack(int windowingMode) {
+    TaskStack getTopStackInWindowingMode(int windowingMode) {
         return getStack(windowingMode, ACTIVITY_TYPE_UNDEFINED);
     }
 
@@ -1744,7 +1756,7 @@
     }
 
     void getContentRect(Rect out) {
-        out.set(mContentRect);
+        out.set(mDisplayFrames.mContent);
     }
 
     TaskStack createStack(int stackId, boolean onTop, StackWindowController controller) {
@@ -1753,10 +1765,6 @@
 
         final TaskStack stack = new TaskStack(mService, stackId, controller);
         mTaskStackContainers.addStackToDisplay(stack, onTop);
-
-        if (stack.inSplitScreenPrimaryWindowingMode()) {
-            mDividerControllerLocked.notifyDockedStackExistsChanged(true);
-        }
         return stack;
     }
 
@@ -1847,8 +1855,8 @@
             mTmpRect2.setEmpty();
             for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
                 final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
-                stack.setTouchExcludeRegion(
-                        focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
+                stack.setTouchExcludeRegion(focusedTask, delta, mTouchExcludeRegion,
+                        mDisplayFrames.mContent, mTmpRect2);
             }
             // If we removed the focused task above, add it back and only leave its
             // outside touch area in the exclusion. TapDectector is not interested in
@@ -1877,7 +1885,7 @@
             mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
         }
         // TODO(multi-display): Support docked stacks on secondary displays.
-        if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStackStack() != null) {
+        if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStack() != null) {
             mDividerControllerLocked.getTouchRegion(mTmpRect);
             mTmpRegion.set(mTmpRect);
             mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -2025,7 +2033,7 @@
         final boolean imeOnTop = (imeDockSide == DOCKED_TOP);
         final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM);
         final boolean dockMinimized = mDividerControllerLocked.isMinimizedDock();
-        final int imeHeight = mService.mPolicy.getInputMethodWindowVisibleHeightLw();
+        final int imeHeight = mDisplayFrames.getInputMethodWindowVisibleHeight();
         final boolean imeHeightChanged = imeVisible &&
                 imeHeight != mDividerControllerLocked.getImeHeightAdjustedFor();
 
@@ -2167,6 +2175,7 @@
         if (screenRotationAnimation != null) {
             screenRotationAnimation.writeToProto(proto, SCREEN_ROTATION_ANIMATION);
         }
+        mDisplayFrames.writeToProto(proto, DISPLAY_FRAMES);
         proto.end(token);
     }
 
@@ -2232,7 +2241,7 @@
         if (pinnedStack != null) {
             pw.println(prefix + "pinnedStack=" + pinnedStack.getName());
         }
-        final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStackStack();
+        final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStack();
         if (splitScreenPrimaryStack != null) {
             pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
         }
@@ -2246,6 +2255,9 @@
             pw.println(subPrefix
                     + "mInputMethodAnimLayerAdjustment=" + mInputMethodAnimLayerAdjustment);
         }
+
+        pw.println();
+        mDisplayFrames.dump(prefix, pw);
     }
 
     @Override
@@ -2259,7 +2271,7 @@
 
     /** Returns true if the stack in the windowing mode is visible. */
     boolean isStackVisible(int windowingMode) {
-        final TaskStack stack = getStack(windowingMode);
+        final TaskStack stack = getTopStackInWindowingMode(windowingMode);
         return stack != null && stack.isVisible();
     }
 
@@ -2871,22 +2883,22 @@
 
         final int dw = mDisplayInfo.logicalWidth;
         final int dh = mDisplayInfo.logicalHeight;
-
         if (DEBUG_LAYOUT) {
             Slog.v(TAG, "-------------------------------------");
             Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh);
         }
 
-        mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation,
-                getConfiguration().uiMode);
+        mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo);
+        // TODO: Not sure if we really need to set the rotation here since we are updating from the
+        // display info above...
+        mDisplayFrames.mRotation = mRotation;
+        mService.mPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
         if (isDefaultDisplay) {
             // Not needed on non-default displays.
             mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
             mService.mScreenRect.set(0, 0, dw, dh);
         }
 
-        mService.mPolicy.getContentRectLw(mContentRect);
-
         int seq = mService.mLayoutSeq + 1;
         if (seq < 0) seq = 0;
         mService.mLayoutSeq = seq;
@@ -2916,7 +2928,6 @@
             mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
         }
 
-        mService.mPolicy.finishLayoutLw();
         mService.mH.sendEmptyMessage(UPDATE_DOCKED_STACK_DIVIDER);
     }
 
@@ -3377,6 +3388,12 @@
             }
             for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
                 final TaskStack stack = mTaskStackContainers.getChildAt(i);
+                if (activityType == ACTIVITY_TYPE_UNDEFINED
+                        && windowingMode == stack.getWindowingMode()) {
+                    // Passing in undefined type means we want to match the topmost stack with the
+                    // windowing mode.
+                    return stack;
+                }
                 if (stack.isCompatible(windowingMode, activityType)) {
                     return stack;
                 }
@@ -3401,7 +3418,7 @@
             return mPinnedStack;
         }
 
-        TaskStack getSplitScreenPrimaryStackStack() {
+        TaskStack getSplitScreenPrimaryStack() {
             return mSplitScreenPrimaryStack;
         }
 
@@ -3447,6 +3464,7 @@
                             + " already exist on display=" + this + " stack=" + stack);
                 }
                 mSplitScreenPrimaryStack = stack;
+                mDividerControllerLocked.notifyDockedStackExistsChanged(true);
             }
         }
 
@@ -3457,6 +3475,10 @@
                 mPinnedStack = null;
             } else if (stack == mSplitScreenPrimaryStack) {
                 mSplitScreenPrimaryStack = null;
+                // Re-set the split-screen create mode whenever the split-screen stack is removed.
+                mService.setDockedStackCreateStateLocked(
+                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
+                mDividerControllerLocked.notifyDockedStackExistsChanged(false);
             }
         }
 
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index 5ce4d46..d79ba89 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -23,6 +23,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
@@ -116,6 +117,7 @@
     private final DimLayer mDimLayer;
 
     private boolean mMinimizedDock;
+    private int mOriginalDockedSide = DOCKED_INVALID;
     private boolean mAnimatingForMinimizedDockedStack;
     private boolean mAnimationStarted;
     private long mAnimationStartTime;
@@ -321,7 +323,7 @@
         if (mWindow == null) {
             return;
         }
-        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
 
         // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
         final boolean visible = stack != null;
@@ -361,7 +363,7 @@
     }
 
     void positionDockedStackedDivider(Rect frame) {
-        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStack();
+        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStack();
         if (stack == null) {
             // Unfortunately we might end up with still having a divider, even though the underlying
             // stack was already removed. This is because we are on AM thread and the removal of the
@@ -408,6 +410,31 @@
         mDockedStackListeners.finishBroadcast();
     }
 
+    /**
+     * Checks if the primary stack is allowed to dock to a specific side based on its original dock
+     * side.
+     *
+     * @param dockSide the side to see if it is valid
+     * @return true if the side provided is valid
+     */
+    boolean canPrimaryStackDockTo(int dockSide) {
+        if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+            // Side is the same as original side
+            if (dockSide == mOriginalDockedSide) {
+                return true;
+            }
+            // Special rule that the top in portrait is always valid
+            if (dockSide == DOCKED_TOP) {
+                return true;
+            }
+            // Only if original docked side was top in portrait will allow left side for landscape
+            if (dockSide == DOCKED_LEFT && mOriginalDockedSide == DOCKED_TOP) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     void notifyDockedStackExistsChanged(boolean exists) {
         // TODO(multi-display): Perform all actions only for current display.
         final int size = mDockedStackListeners.beginBroadcast();
@@ -430,8 +457,11 @@
                 inputMethodManagerInternal.hideCurrentInputMethod();
                 mImeHideRequested = true;
             }
+            final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+            mOriginalDockedSide = stack.getDockSide();
             return;
         }
+        mOriginalDockedSide = DOCKED_INVALID;
         setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
     }
 
@@ -458,7 +488,7 @@
         long animDuration = 0;
         if (animate) {
             final TaskStack stack =
-                    mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+                    mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
             final long transitionDuration = isAnimationMaximizing()
                     ? mService.mAppTransition.getLastClipRevealTransitionDuration()
                     : DEFAULT_APP_TRANSITION_DURATION;
@@ -513,7 +543,7 @@
         mDockedStackListeners.register(listener);
         notifyDockedDividerVisibilityChanged(wasVisible());
         notifyDockedStackExistsChanged(
-                mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null);
+                mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null);
         notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
                 isHomeStackResizable());
         notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
@@ -529,9 +559,9 @@
     void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
         // TODO: Maybe only allow split-screen windowing modes?
         final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
-                ? mDisplayContent.getStack(targetWindowingMode)
+                ? mDisplayContent.getTopStackInWindowingMode(targetWindowingMode)
                 : null;
-        final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackStack();
+        final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
             stack.getDimBounds(mTmpRect);
@@ -543,7 +573,7 @@
                         mDimLayer.setBounds(mTmpRect);
                         mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
                     } finally {
-                        mService.closeSurfaceTransaction();
+                        mService.closeSurfaceTransaction("setResizeDimLayer");
                     }
                 }
                 mLastDimLayerRect.set(mTmpRect);
@@ -558,7 +588,7 @@
                     mService.openSurfaceTransaction();
                     mDimLayer.hide();
                 } finally {
-                    mService.closeSurfaceTransaction();
+                    mService.closeSurfaceTransaction("setResizeDimLayer");
                 }
             }
             mLastDimLayerAlpha = 0f;
@@ -616,7 +646,7 @@
     }
 
     private void checkMinimizeChanged(boolean animate) {
-        if (mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() == null) {
+        if (mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility() == null) {
             return;
         }
         final TaskStack homeStack = mDisplayContent.getHomeStack();
@@ -633,11 +663,15 @@
         if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
             return;
         }
-        final TaskStack fullscreenStack = mDisplayContent.getStack(
+        final TaskStack topSecondaryStack = mDisplayContent.getTopStackInWindowingMode(
                 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
-        final boolean homeBehind = fullscreenStack != null && fullscreenStack.isVisible();
-        setMinimizedDockedStack(homeVisible && !homeBehind, animate);
+        boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
+        if (homeVisible && topSecondaryStack != null) {
+            // Home should only be considered visible if it is greater or equal to the top secondary
+            // stack in terms of z-order.
+            homeVisible = homeStack.compareTo(topSecondaryStack) >= 0;
+        }
+        setMinimizedDockedStack(homeVisible, animate);
     }
 
     private boolean isWithinDisplay(Task task) {
@@ -778,7 +812,7 @@
     }
 
     private boolean setMinimizedDockedStack(boolean minimized) {
-        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
         notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
         return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
     }
@@ -829,7 +863,7 @@
     }
 
     private boolean animateForMinimizedDockedStack(long now) {
-        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
diff --git a/com/android/server/wm/DragDropController.java b/com/android/server/wm/DragDropController.java
index a3c6167..4567e10 100644
--- a/com/android/server/wm/DragDropController.java
+++ b/com/android/server/wm/DragDropController.java
@@ -21,10 +21,13 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.annotation.NonNull;
 import android.content.ClipData;
 import android.graphics.PixelFormat;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.util.Slog;
 import android.view.Display;
@@ -33,7 +36,9 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
-import com.android.server.wm.WindowManagerService.H;
+import android.view.WindowManagerInternal.IDragDropCallback;
+import com.android.internal.util.Preconditions;
+import com.android.server.input.InputWindowHandle;
 
 /**
  * Managing drag and drop operations initiated by View#startDragAndDrop.
@@ -41,13 +46,88 @@
 class DragDropController {
     private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
     private static final long DRAG_TIMEOUT_MS = 5000;
-    DragState mDragState;
+
+    // Messages for Handler.
+    private static final int MSG_DRAG_START_TIMEOUT = 0;
+    static final int MSG_DRAG_END_TIMEOUT = 1;
+    static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 2;
+    static final int MSG_ANIMATION_END = 3;
+
+    /**
+     * Drag state per operation.
+     * Needs a lock of {@code WindowManagerService#mWindowMap} to read this. Needs both locks of
+     * {@code mWriteLock} and {@code WindowManagerService#mWindowMap} to update this.
+     * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState
+     * itself, thus the variable can be null after calling DragState's methods.
+     */
+    private DragState mDragState;
+
+    private WindowManagerService mService;
+    private final Handler mHandler;
+
+    /**
+     * Lock to preserve the order of state updates.
+     * The lock is used to process drag and drop state updates in order without having the window
+     * manager lock.
+     *
+     * Suppose DragDropController invokes a callback method A, then processes the following update
+     * A'. Same for a callback method B and the following update B'. The callback wants
+     * DragDropController to processes the updates in the order of  A' then B'.
+     *
+     * Without mWriteLock: the following race can happen.
+     *
+     * 1. Thread a calls A.
+     * 2. Thread b calls B.
+     * 3. Thread b acquires the window manager lock
+     * 4. thread b processes the update B'
+     * 5. Thread a acquires the window manager lock
+     * 6. thread a processes the update A'
+     *
+     * With mWriteLock we can ensure the order of A' and B'
+     *
+     * 1. Thread a acquire mWriteLock
+     * 2. Thread a calls A
+     * 3. Thread a acquire the window manager lock
+     * 4. Thread a processes A'
+     * 5. Thread b acquire mWriteLock
+     * 6. Thread b calls B
+     * 7. Thread b acquire the window manager lock
+     * 8. Thread b processes B'
+     *
+     * Don't acquire the lock while holding the window manager lock, otherwise it causes a deadlock.
+     */
+    private final Object mWriteLock = new Object();
+
+    /**
+     * Callback which is used to sync drag state with the vendor-specific code.
+     */
+    @NonNull private IDragDropCallback mCallback = new IDragDropCallback() {};
 
     boolean dragDropActiveLocked() {
         return mDragState != null;
     }
 
-    IBinder prepareDrag(WindowManagerService service, SurfaceSession session, int callerPid,
+    InputWindowHandle getInputWindowHandleLocked() {
+        return mDragState.getInputWindowHandle();
+    }
+
+    void registerCallback(IDragDropCallback callback) {
+        Preconditions.checkNotNull(callback);
+        synchronized (mWriteLock) {
+            mCallback = callback;
+        }
+    }
+
+    DragDropController(WindowManagerService service, Looper looper) {
+        mService = service;
+        mHandler = new DragHandler(service, looper);
+    }
+
+    void sendDragStartedIfNeededLocked(WindowState window) {
+        mDragState.sendDragStartedIfNeededLocked(window);
+    }
+
+    IBinder prepareDrag(SurfaceSession session, int callerPid,
             int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) {
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
@@ -55,188 +135,223 @@
                     + " asbinder=" + window.asBinder());
         }
 
-        IBinder token = null;
+        synchronized (mWriteLock) {
+            synchronized (mService.mWindowMap) {
+                if (dragDropActiveLocked()) {
+                    Slog.w(TAG_WM, "Drag already in progress");
+                    return null;
+                }
 
-        synchronized (service.mWindowMap) {
-            if (dragDropActiveLocked()) {
-                Slog.w(TAG_WM, "Drag already in progress");
-                return null;
+                // TODO(multi-display): support other displays
+                final DisplayContent displayContent =
+                        mService.getDefaultDisplayContentLocked();
+                final Display display = displayContent.getDisplay();
+
+                final SurfaceControl surface = new SurfaceControl.Builder(session)
+                        .setName("drag surface")
+                        .setSize(width, height)
+                        .setFormat(PixelFormat.TRANSLUCENT)
+                        .build();
+                surface.setLayerStack(display.getLayerStack());
+                float alpha = 1;
+                if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
+                    alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
+                }
+                surface.setAlpha(alpha);
+
+                if (SHOW_TRANSACTIONS)
+                    Slog.i(TAG_WM, "  DRAG " + surface + ": CREATE");
+                outSurface.copyFrom(surface);
+                final IBinder winBinder = window.asBinder();
+                IBinder token = new Binder();
+                mDragState = new DragState(mService, token, surface, flags, winBinder);
+                mDragState.mPid = callerPid;
+                mDragState.mUid = callerUid;
+                mDragState.mOriginalAlpha = alpha;
+                token = mDragState.mToken = new Binder();
+
+                // 5 second timeout for this window to actually begin the drag
+                sendTimeoutMessage(MSG_DRAG_START_TIMEOUT, winBinder);
+                return token;
             }
-
-            // TODO(multi-display): support other displays
-            final DisplayContent displayContent =
-                    service.getDefaultDisplayContentLocked();
-            final Display display = displayContent.getDisplay();
-
-            final SurfaceControl surface = new SurfaceControl.Builder(session)
-                    .setName("drag surface")
-                    .setSize(width, height)
-                    .setFormat(PixelFormat.TRANSLUCENT)
-                    .build();
-            surface.setLayerStack(display.getLayerStack());
-            float alpha = 1;
-            if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
-                alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
-            }
-            surface.setAlpha(alpha);
-
-            if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG " + surface + ": CREATE");
-            outSurface.copyFrom(surface);
-            final IBinder winBinder = window.asBinder();
-            token = new Binder();
-            mDragState = new DragState(service, token, surface, flags, winBinder);
-            mDragState.mPid = callerPid;
-            mDragState.mUid = callerUid;
-            mDragState.mOriginalAlpha = alpha;
-            token = mDragState.mToken = new Binder();
-
-            // 5 second timeout for this window to actually begin the drag
-            service.mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder);
-            Message msg = service.mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder);
-            service.mH.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
         }
-
-        return token;
     }
 
-    boolean performDrag(WindowManagerService service, IWindow window, IBinder dragToken,
+    boolean performDrag(IWindow window, IBinder dragToken,
             int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
             ClipData data) {
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
         }
 
-        synchronized (service.mWindowMap) {
-            if (mDragState == null) {
-                Slog.w(TAG_WM, "No drag prepared");
-                throw new IllegalStateException("performDrag() without prepareDrag()");
-            }
-
-            if (dragToken != mDragState.mToken) {
-                Slog.w(TAG_WM, "Performing mismatched drag");
-                throw new IllegalStateException("performDrag() does not match prepareDrag()");
-            }
-
-            final WindowState callingWin = service.windowForClientLocked(null, window, false);
-            if (callingWin == null) {
-                Slog.w(TAG_WM, "Bad requesting window " + window);
-                return false;  // !!! TODO: throw here?
-            }
-
-            // !!! TODO: if input is not still focused on the initiating window, fail
-            // the drag initiation (e.g. an alarm window popped up just as the application
-            // called performDrag()
-
-            service.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
-
-            // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
-            // will let us eliminate the (touchX,touchY) parameters from the API.
-
-            // !!! FIXME: put all this heavy stuff onto the mH looper, as well as
-            // the actual drag event dispatch stuff in the dragstate
-
-            final DisplayContent displayContent = callingWin.getDisplayContent();
-            if (displayContent == null) {
+        synchronized (mWriteLock) {
+            if (!mCallback.performDrag(window, dragToken, touchSource, touchX, touchY, thumbCenterX,
+                    thumbCenterY, data)) {
                 return false;
             }
-            Display display = displayContent.getDisplay();
-            mDragState.register(display);
-            if (!service.mInputManager.transferTouchFocus(callingWin.mInputChannel,
-                    mDragState.getInputChannel())) {
-                Slog.e(TAG_WM, "Unable to transfer touch focus");
-                mDragState.unregister();
-                mDragState.reset();
-                mDragState = null;
-                return false;
+            synchronized (mService.mWindowMap) {
+                if (mDragState == null) {
+                    Slog.w(TAG_WM, "No drag prepared");
+                    throw new IllegalStateException("performDrag() without prepareDrag()");
+                }
+
+                if (dragToken != mDragState.mToken) {
+                    Slog.w(TAG_WM, "Performing mismatched drag");
+                    throw new IllegalStateException("performDrag() does not match prepareDrag()");
+                }
+
+                final WindowState callingWin = mService.windowForClientLocked(null, window, false);
+                if (callingWin == null) {
+                    Slog.w(TAG_WM, "Bad requesting window " + window);
+                    return false;  // !!! TODO: throw here?
+                }
+
+                // !!! TODO: if input is not still focused on the initiating window, fail
+                // the drag initiation (e.g. an alarm window popped up just as the application
+                // called performDrag()
+
+                mHandler.removeMessages(MSG_DRAG_START_TIMEOUT, window.asBinder());
+
+                // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
+                // will let us eliminate the (touchX,touchY) parameters from the API.
+
+                // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
+                // the actual drag event dispatch stuff in the dragstate
+
+                final DisplayContent displayContent = callingWin.getDisplayContent();
+                if (displayContent == null) {
+                    return false;
+                }
+                Display display = displayContent.getDisplay();
+                mDragState.register(display);
+                if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
+                        mDragState.getInputChannel())) {
+                    Slog.e(TAG_WM, "Unable to transfer touch focus");
+                    mDragState.closeLocked();
+                    return false;
+                }
+
+                mDragState.mDisplayContent = displayContent;
+                mDragState.mData = data;
+                mDragState.broadcastDragStartedLocked(touchX, touchY);
+                mDragState.overridePointerIconLocked(touchSource);
+
+                // remember the thumb offsets for later
+                mDragState.mThumbOffsetX = thumbCenterX;
+                mDragState.mThumbOffsetY = thumbCenterY;
+
+                // Make the surface visible at the proper location
+                final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
+                mService.openSurfaceTransaction();
+                try {
+                    surfaceControl.setPosition(touchX - thumbCenterX,
+                            touchY - thumbCenterY);
+                    surfaceControl.setLayer(mDragState.getDragLayerLocked());
+                    surfaceControl.setLayerStack(display.getLayerStack());
+                    surfaceControl.show();
+                } finally {
+                    mService.closeSurfaceTransaction("performDrag");
+                    if (SHOW_LIGHT_TRANSACTIONS) {
+                        Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+                    }
+                }
+
+                mDragState.notifyLocationLocked(touchX, touchY);
             }
-
-            mDragState.mDisplayContent = displayContent;
-            mDragState.mData = data;
-            mDragState.broadcastDragStartedLw(touchX, touchY);
-            mDragState.overridePointerIconLw(touchSource);
-
-            // remember the thumb offsets for later
-            mDragState.mThumbOffsetX = thumbCenterX;
-            mDragState.mThumbOffsetY = thumbCenterY;
-
-            // Make the surface visible at the proper location
-            final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                    TAG_WM, ">>> OPEN TRANSACTION performDrag");
-            service.openSurfaceTransaction();
-            try {
-                surfaceControl.setPosition(touchX - thumbCenterX,
-                        touchY - thumbCenterY);
-                surfaceControl.setLayer(mDragState.getDragLayerLw());
-                surfaceControl.setLayerStack(display.getLayerStack());
-                surfaceControl.show();
-            } finally {
-                service.closeSurfaceTransaction();
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                        TAG_WM, "<<< CLOSE TRANSACTION performDrag");
-            }
-
-            mDragState.notifyLocationLw(touchX, touchY);
         }
 
         return true;    // success!
     }
 
-    void reportDropResult(WindowManagerService service, IWindow window, boolean consumed) {
+    void reportDropResult(IWindow window, boolean consumed) {
         IBinder token = window.asBinder();
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
         }
 
-        synchronized (service.mWindowMap) {
-            if (mDragState == null) {
-                // Most likely the drop recipient ANRed and we ended the drag
-                // out from under it.  Log the issue and move on.
-                Slog.w(TAG_WM, "Drop result given but no drag in progress");
-                return;
-            }
+        synchronized (mWriteLock) {
+            mCallback.reportDropResult(window, consumed);
+            synchronized (mService.mWindowMap) {
+                if (mDragState == null) {
+                    // Most likely the drop recipient ANRed and we ended the drag
+                    // out from under it.  Log the issue and move on.
+                    Slog.w(TAG_WM, "Drop result given but no drag in progress");
+                    return;
+                }
 
-            if (mDragState.mToken != token) {
-                // We're in a drag, but the wrong window has responded.
-                Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
-                throw new IllegalStateException("reportDropResult() by non-recipient");
-            }
+                if (mDragState.mToken != token) {
+                    // We're in a drag, but the wrong window has responded.
+                    Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
+                    throw new IllegalStateException("reportDropResult() by non-recipient");
+                }
 
-            // The right window has responded, even if it's no longer around,
-            // so be sure to halt the timeout even if the later WindowState
-            // lookup fails.
-            service.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
-            WindowState callingWin = service.windowForClientLocked(null, window, false);
-            if (callingWin == null) {
-                Slog.w(TAG_WM, "Bad result-reporting window " + window);
-                return;  // !!! TODO: throw here?
-            }
+                // The right window has responded, even if it's no longer around,
+                // so be sure to halt the timeout even if the later WindowState
+                // lookup fails.
+                mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
+                WindowState callingWin = mService.windowForClientLocked(null, window, false);
+                if (callingWin == null) {
+                    Slog.w(TAG_WM, "Bad result-reporting window " + window);
+                    return;  // !!! TODO: throw here?
+                }
 
-            mDragState.mDragResult = consumed;
-            mDragState.endDragLw();
+
+                mDragState.mDragResult = consumed;
+                mDragState.endDragLocked();
+            }
         }
     }
 
-    void cancelDragAndDrop(WindowManagerService service, IBinder dragToken) {
+    void cancelDragAndDrop(IBinder dragToken) {
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "cancelDragAndDrop");
         }
 
-        synchronized (service.mWindowMap) {
-            if (mDragState == null) {
-                Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
-                throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
-            }
+        synchronized (mWriteLock) {
+            mCallback.cancelDragAndDrop(dragToken);
+            synchronized (mService.mWindowMap) {
+                if (mDragState == null) {
+                    Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
+                    throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
+                }
 
-            if (mDragState.mToken != dragToken) {
-                Slog.w(TAG_WM,
-                        "cancelDragAndDrop() does not match prepareDrag()");
-                throw new IllegalStateException(
-                        "cancelDragAndDrop() does not match prepareDrag()");
-            }
+                if (mDragState.mToken != dragToken) {
+                    Slog.w(TAG_WM,
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                    throw new IllegalStateException(
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                }
 
-            mDragState.mDragResult = false;
-            mDragState.cancelDragLw();
+                mDragState.mDragResult = false;
+                mDragState.cancelDragLocked();
+            }
+        }
+    }
+
+    /**
+     * Handles motion events.
+     * @param keepHandling Whether if the drag operation is continuing or this is the last motion
+     *          event.
+     * @param newX X coordinate value in dp in the screen coordinate
+     * @param newY Y coordinate value in dp in the screen coordinate
+     */
+    void handleMotionEvent(boolean keepHandling, float newX, float newY) {
+        synchronized (mWriteLock) {
+            synchronized (mService.mWindowMap) {
+                if (!dragDropActiveLocked()) {
+                    // The drag has ended but the clean-up message has not been processed by
+                    // window manager. Drop events that occur after this until window manager
+                    // has a chance to clean-up the input handle.
+                    return;
+                }
+
+                if (keepHandling) {
+                    mDragState.notifyMoveLocked(newX, newY);
+                } else {
+                    mDragState.notifyDropLocked(newX, newY);
+                }
+            }
         }
     }
 
@@ -252,49 +367,105 @@
         }
     }
 
-    void handleMessage(WindowManagerService service, Message msg) {
-        switch (msg.what) {
-            case H.DRAG_START_TIMEOUT: {
-                IBinder win = (IBinder) msg.obj;
-                if (DEBUG_DRAG) {
-                    Slog.w(TAG_WM, "Timeout starting drag by win " + win);
-                }
-                synchronized (service.mWindowMap) {
-                    // !!! TODO: ANR the app that has failed to start the drag in time
-                    if (mDragState != null) {
-                        mDragState.unregister();
-                        mDragState.reset();
-                        mDragState = null;
-                    }
-                }
-                break;
-            }
+    /**
+     * Sends a message to the Handler managed by DragDropController.
+     */
+    void sendHandlerMessage(int what, Object arg) {
+        mHandler.obtainMessage(what, arg).sendToTarget();
+    }
 
-            case H.DRAG_END_TIMEOUT: {
-                IBinder win = (IBinder) msg.obj;
-                if (DEBUG_DRAG) {
-                    Slog.w(TAG_WM, "Timeout ending drag to win " + win);
-                }
-                synchronized (service.mWindowMap) {
-                    // !!! TODO: ANR the drag-receiving app
-                    if (mDragState != null) {
-                        mDragState.mDragResult = false;
-                        mDragState.endDragLw();
-                    }
-                }
-                break;
-            }
+    /**
+     * Sends a timeout message to the Handler managed by DragDropController.
+     */
+    void sendTimeoutMessage(int what, Object arg) {
+        mHandler.removeMessages(what, arg);
+        final Message msg = mHandler.obtainMessage(what, arg);
+        mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
+    }
 
-            case H.TEAR_DOWN_DRAG_AND_DROP_INPUT: {
-                if (DEBUG_DRAG)
-                    Slog.d(TAG_WM, "Drag ending; tearing down input channel");
-                DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
-                if (interceptor != null) {
-                    synchronized (service.mWindowMap) {
+    /**
+     * Notifies the current drag state is closed.
+     */
+    void onDragStateClosedLocked(DragState dragState) {
+        if (mDragState != dragState) {
+            Slog.wtf(TAG_WM, "Unknown drag state is closed");
+            return;
+        }
+        mDragState = null;
+    }
+
+    private class DragHandler extends Handler {
+        /**
+         * Lock for window manager.
+         */
+        private final WindowManagerService mService;
+
+        DragHandler(WindowManagerService service, Looper looper) {
+            super(looper);
+            mService = service;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DRAG_START_TIMEOUT: {
+                    IBinder win = (IBinder) msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG_WM, "Timeout starting drag by win " + win);
+                    }
+                    synchronized (mWriteLock) {
+                        synchronized (mService.mWindowMap) {
+                            // !!! TODO: ANR the app that has failed to start the drag in time
+                            if (mDragState != null) {
+                                mDragState.closeLocked();
+                            }
+                        }
+                    }
+                    break;
+                }
+
+                case MSG_DRAG_END_TIMEOUT: {
+                    final IBinder win = (IBinder) msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG_WM, "Timeout ending drag to win " + win);
+                    }
+                    synchronized (mWriteLock) {
+                        synchronized (mService.mWindowMap) {
+                            // !!! TODO: ANR the drag-receiving app
+                            if (mDragState != null) {
+                                mDragState.mDragResult = false;
+                                mDragState.endDragLocked();
+                            }
+                        }
+                    }
+                    break;
+                }
+
+                case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: {
+                    if (DEBUG_DRAG)
+                        Slog.d(TAG_WM, "Drag ending; tearing down input channel");
+                    final DragState.InputInterceptor interceptor =
+                            (DragState.InputInterceptor) msg.obj;
+                    if (interceptor == null) return;
+                    synchronized (mService.mWindowMap) {
                         interceptor.tearDown();
                     }
+                    break;
                 }
-                break;
+
+                case MSG_ANIMATION_END: {
+                    synchronized (mWriteLock) {
+                        synchronized (mService.mWindowMap) {
+                            if (mDragState == null) {
+                                Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
+                                        "plyaing animation");
+                                return;
+                            }
+                            mDragState.closeLocked();
+                        }
+                    }
+                    break;
+                }
             }
         }
     }
diff --git a/com/android/server/wm/DragInputEventReceiver.java b/com/android/server/wm/DragInputEventReceiver.java
new file mode 100644
index 0000000..bee2bac
--- /dev/null
+++ b/com/android/server/wm/DragInputEventReceiver.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.os.Looper;
+import android.util.Slog;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+
+/**
+ * Input receiver for drag and drop
+ */
+class DragInputEventReceiver extends InputEventReceiver {
+    private final DragDropController mDragDropController;
+
+    // Set, if stylus button was down at the start of the drag.
+    private boolean mStylusButtonDownAtStart;
+    // Indicates the first event to check for button state.
+    private boolean mIsStartEvent = true;
+    // Set to true to ignore input events after the drag gesture is complete but the drag events
+    // are still being dispatched.
+    private boolean mMuteInput = false;
+
+    DragInputEventReceiver(InputChannel inputChannel, Looper looper,
+            DragDropController controller) {
+        super(inputChannel, looper);
+        mDragDropController = controller;
+    }
+
+    @Override
+    public void onInputEvent(InputEvent event, int displayId) {
+        boolean handled = false;
+        try {
+            if (!(event instanceof MotionEvent)
+                    || (event.getSource() & SOURCE_CLASS_POINTER) == 0
+                    || mMuteInput) {
+                return;
+            }
+            final MotionEvent motionEvent = (MotionEvent) event;
+            final float newX = motionEvent.getRawX();
+            final float newY = motionEvent.getRawY();
+            final boolean isStylusButtonDown =
+                    (motionEvent.getButtonState() & BUTTON_STYLUS_PRIMARY) != 0;
+
+            if (mIsStartEvent) {
+                // First event and the button was down, check for the button being
+                // lifted in the future, if that happens we'll drop the item.
+                mStylusButtonDownAtStart = isStylusButtonDown;
+                mIsStartEvent = false;
+            }
+
+            switch (motionEvent.getAction()) {
+                case ACTION_DOWN:
+                    if (DEBUG_DRAG) Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
+                    return;
+                case ACTION_MOVE:
+                    if (mStylusButtonDownAtStart && !isStylusButtonDown) {
+                        if (DEBUG_DRAG) {
+                            Slog.d(TAG_WM, "Button no longer pressed; dropping at " + newX + ","
+                                    + newY);
+                        }
+                        mMuteInput = true;
+                    }
+                    break;
+                case ACTION_UP:
+                    if (DEBUG_DRAG) {
+                        Slog.d(TAG_WM, "Got UP on move channel; dropping at " + newX + "," + newY);
+                    }
+                    mMuteInput = true;
+                    break;
+                case ACTION_CANCEL:
+                    if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
+                    mMuteInput = true;
+                    break;
+                default:
+                    return;
+            }
+
+            mDragDropController.handleMotionEvent(!mMuteInput /* keepHandling */, newX, newY);
+            handled = true;
+        } catch (Exception e) {
+            Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
+        } finally {
+            finishInputEvent(event, handled);
+        }
+    }
+}
diff --git a/com/android/server/wm/DragState.java b/com/android/server/wm/DragState.java
index 11f2241..e81d366 100644
--- a/com/android/server/wm/DragState.java
+++ b/com/android/server/wm/DragState.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
+import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT;
+import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -29,14 +32,10 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Context;
-import android.graphics.Matrix;
 import android.graphics.Point;
 import android.hardware.input.InputManager;
 import android.os.Build;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -50,19 +49,14 @@
 import android.view.InputDevice;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
-import android.view.animation.Transformation;
-
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
-import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
-import com.android.server.wm.WindowManagerService.H;
 
 import com.android.internal.view.IDragAndDropPermissions;
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
 
 import java.util.ArrayList;
 
@@ -86,10 +80,8 @@
     private static final String ANIMATED_PROPERTY_ALPHA = "alpha";
     private static final String ANIMATED_PROPERTY_SCALE = "scale";
 
-    // Messages for Handler.
-    private static final int MSG_ANIMATION_END = 0;
-
     final WindowManagerService mService;
+    final DragDropController mDragDropController;
     IBinder mToken;
     /**
      * Do not use the variable from the out of animation thread while mAnimator is not null.
@@ -113,39 +105,101 @@
     WindowState mTargetWindow;
     ArrayList<WindowState> mNotifiedWindows;
     boolean mDragInProgress;
+    /**
+     * Whether if animation is completed. Needs to be volatile to update from the animation thread
+     * without having a WM lock.
+     */
+    volatile boolean mAnimationCompleted = false;
     DisplayContent mDisplayContent;
 
     @Nullable private ValueAnimator mAnimator;
     private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
     private Point mDisplaySize = new Point();
-    private final Handler mHandler;
 
     DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
             int flags, IBinder localWin) {
         mService = service;
+        mDragDropController = service.mDragDropController;
         mToken = token;
         mSurfaceControl = surface;
         mFlags = flags;
         mLocalWin = localWin;
         mNotifiedWindows = new ArrayList<WindowState>();
-        mHandler = new DragStateHandler(service.mH.getLooper());
     }
 
-    void reset() {
-        if (mAnimator != null) {
+    /**
+     * After calling this, DragDropController#onDragStateClosedLocked is invoked, which causes
+     * DragDropController#mDragState becomes null.
+     */
+    void closeLocked() {
+        // Unregister the input interceptor.
+        if (mInputInterceptor != null) {
+            if (DEBUG_DRAG)
+                Slog.d(TAG_WM, "unregistering drag input channel");
+
+            // Input channel should be disposed on the thread where the input is being handled.
+            mDragDropController.sendHandlerMessage(
+                    MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor);
+            mInputInterceptor = null;
+            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+        }
+
+        // Send drag end broadcast if drag start has been sent.
+        if (mDragInProgress) {
+            final int myPid = Process.myPid();
+
+            if (DEBUG_DRAG) {
+                Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
+            }
+            for (WindowState ws : mNotifiedWindows) {
+                float x = 0;
+                float y = 0;
+                if (!mDragResult && (ws.mSession.mPid == mPid)) {
+                    // Report unconsumed drop location back to the app that started the drag.
+                    x = mCurrentX;
+                    y = mCurrentY;
+                }
+                DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+                        x, y, null, null, null, null, mDragResult);
+                try {
+                    ws.mClient.dispatchDragEvent(evt);
+                } catch (RemoteException e) {
+                    Slog.w(TAG_WM, "Unable to drag-end window " + ws);
+                }
+                // if the current window is in the same process,
+                // the dispatch has already recycled the event
+                if (myPid != ws.mSession.mPid) {
+                    evt.recycle();
+                }
+            }
+            mNotifiedWindows.clear();
+            mDragInProgress = false;
+        }
+
+        // Take the cursor back if it has been changed.
+        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
+            mTouchSource = 0;
+        }
+
+        // Clear the internal variables.
+        if (mSurfaceControl != null) {
+            mSurfaceControl.destroy();
+            mSurfaceControl = null;
+        }
+        if (mAnimator != null && !mAnimationCompleted) {
             Slog.wtf(TAG_WM,
                     "Unexpectedly destroying mSurfaceControl while animation is running");
         }
-        if (mSurfaceControl != null) {
-            mSurfaceControl.destroy();
-        }
-        mSurfaceControl = null;
         mFlags = 0;
         mLocalWin = null;
         mToken = null;
         mData = null;
         mThumbOffsetX = mThumbOffsetY = 0;
         mNotifiedWindows = null;
+
+        // Notifies the controller that the drag state is closed.
+        mDragDropController.onDragStateClosedLocked(this);
     }
 
     class InputInterceptor {
@@ -159,8 +213,8 @@
             mServerChannel = channels[0];
             mClientChannel = channels[1];
             mService.mInputManager.registerInputChannel(mServerChannel, null);
-            mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
-                    mService.mH.getLooper());
+            mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
+                    mService.mH.getLooper(), mDragDropController);
 
             mDragApplicationHandle = new InputApplicationHandle(null);
             mDragApplicationHandle.name = "drag";
@@ -171,7 +225,7 @@
                     display.getDisplayId());
             mDragWindowHandle.name = "drag";
             mDragWindowHandle.inputChannel = mServerChannel;
-            mDragWindowHandle.layer = getDragLayerLw();
+            mDragWindowHandle.layer = getDragLayerLocked();
             mDragWindowHandle.layoutParamsFlags = 0;
             mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
             mDragWindowHandle.dispatchingTimeoutNanos =
@@ -244,20 +298,7 @@
         }
     }
 
-    void unregister() {
-        if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel");
-        if (mInputInterceptor == null) {
-            Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
-        } else {
-            // Input channel should be disposed on the thread where the input is being handled.
-            mService.mH.obtainMessage(
-                    H.TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor).sendToTarget();
-            mInputInterceptor = null;
-            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
-        }
-    }
-
-    int getDragLayerLw() {
+    int getDragLayerLocked() {
         return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_DRAG)
                 * WindowManagerService.TYPE_LAYER_MULTIPLIER
                 + WindowManagerService.TYPE_LAYER_OFFSET;
@@ -265,7 +306,7 @@
 
     /* call out to each visible window/session informing it about the drag
      */
-    void broadcastDragStartedLw(final float touchX, final float touchY) {
+    void broadcastDragStartedLocked(final float touchX, final float touchY) {
         mOriginalX = mCurrentX = touchX;
         mOriginalY = mCurrentY = touchY;
 
@@ -292,7 +333,7 @@
         }
 
         mDisplayContent.forAllWindows(w -> {
-            sendDragStartedLw(w, touchX, touchY, mDataDescription);
+            sendDragStartedLocked(w, touchX, touchY, mDataDescription);
         }, false /* traverseTopToBottom */ );
     }
 
@@ -304,7 +345,7 @@
      * This method clones the 'event' parameter if it's being delivered to the same
      * process, so it's safe for the caller to call recycle() on the event afterwards.
      */
-    private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
+    private void sendDragStartedLocked(WindowState newWin, float touchX, float touchY,
             ClipDescription desc) {
         if (mDragInProgress && isValidDropTarget(newWin)) {
             DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
@@ -353,7 +394,7 @@
      * previously been notified, i.e. it became visible after the drag operation
      * was begun.  This is a rare case.
      */
-    void sendDragStartedIfNeededLw(WindowState newWin) {
+    void sendDragStartedIfNeededLocked(WindowState newWin) {
         if (mDragInProgress) {
             // If we have sent the drag-started, we needn't do so again
             if (isWindowNotified(newWin)) {
@@ -362,7 +403,7 @@
             if (DEBUG_DRAG) {
                 Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
             }
-            sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
+            sendDragStartedLocked(newWin, mCurrentX, mCurrentY, mDataDescription);
         }
     }
 
@@ -375,82 +416,33 @@
         return false;
     }
 
-    private void broadcastDragEndedLw() {
-        final int myPid = Process.myPid();
-
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
-        }
-        for (WindowState ws : mNotifiedWindows) {
-            float x = 0;
-            float y = 0;
-            if (!mDragResult && (ws.mSession.mPid == mPid)) {
-                // Report unconsumed drop location back to the app that started the drag.
-                x = mCurrentX;
-                y = mCurrentY;
-            }
-            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
-                    x, y, null, null, null, null, mDragResult);
-            try {
-                ws.mClient.dispatchDragEvent(evt);
-            } catch (RemoteException e) {
-                Slog.w(TAG_WM, "Unable to drag-end window " + ws);
-            }
-            // if the current window is in the same process,
-            // the dispatch has already recycled the event
-            if (myPid != ws.mSession.mPid) {
-                evt.recycle();
-            }
-        }
-        mNotifiedWindows.clear();
-        mDragInProgress = false;
-    }
-
-    void endDragLw() {
+    void endDragLocked() {
         if (mAnimator != null) {
             return;
         }
         if (!mDragResult) {
             mAnimator = createReturnAnimationLocked();
-            return;  // Will call cleanUpDragLw when the animation is done.
+            return;  // Will call closeLocked() when the animation is done.
         }
-        cleanUpDragLw();
+        closeLocked();
     }
 
-    void cancelDragLw() {
+    void cancelDragLocked() {
         if (mAnimator != null) {
             return;
         }
         if (!mDragInProgress) {
             // This can happen if an app invokes Session#cancelDragAndDrop before
-            // Session#performDrag. Reset the drag state:
-            // 1. without sending the end broadcast because the start broadcast has not been sent,
-            // and
-            // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to
-            //    WindowManagerService, which will cause DragState#reset() while playing the
-            //    cancel animation.
-            reset();
-            mService.mDragDropController.mDragState = null;
+            // Session#performDrag. Reset the drag state without playing the cancel animation
+            // because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause
+            // DragState#reset() while playing the cancel animation.
+            closeLocked();
             return;
         }
         mAnimator = createCancelAnimationLocked();
     }
 
-    private void cleanUpDragLw() {
-        broadcastDragEndedLw();
-        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
-            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
-        }
-
-        // stop intercepting input
-        unregister();
-
-        // free our resources and drop all the object references
-        reset();
-        mService.mDragDropController.mDragState = null;
-    }
-
-    void notifyMoveLw(float x, float y) {
+    void notifyMoveLocked(float x, float y) {
         if (mAnimator != null) {
             return;
         }
@@ -459,7 +451,7 @@
 
         // Move the surface to the given touch
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
+                TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
         mService.openSurfaceTransaction();
         try {
             mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
@@ -467,14 +459,14 @@
                     + mSurfaceControl + ": pos=(" +
                     (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("notifyMoveLw");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                    TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
+                    TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
         }
-        notifyLocationLw(x, y);
+        notifyLocationLocked(x, y);
     }
 
-    void notifyLocationLw(float x, float y) {
+    void notifyLocationLocked(float x, float y) {
         // Tell the affected window
         WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
         if (touchedWin != null && !isWindowNotified(touchedWin)) {
@@ -516,34 +508,33 @@
         mTargetWindow = touchedWin;
     }
 
-    // Find the drop target and tell it about the data.  Returns 'true' if we can immediately
-    // dispatch the global drag-ended message, 'false' if we need to wait for a
-    // result from the recipient.
-    boolean notifyDropLw(float x, float y) {
+    /**
+     * Finds the drop target and tells it about the data. If the drop event is not sent to the
+     * target, invokes {@code endDragLocked} immediately.
+     */
+    void notifyDropLocked(float x, float y) {
         if (mAnimator != null) {
-            return false;
+            return;
         }
         mCurrentX = x;
         mCurrentY = y;
 
-        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
+        final WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
 
         if (!isWindowNotified(touchedWin)) {
             // "drop" outside a valid window -- no recipient to apply a
             // timeout to, and we can send the drag-ended message immediately.
             mDragResult = false;
-            return true;
+            endDragLocked();
+            return;
         }
 
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "sending DROP to " + touchedWin);
-        }
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
 
         final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
 
-        DragAndDropPermissionsHandler dragAndDropPermissions = null;
-        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
-                (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+        final DragAndDropPermissionsHandler dragAndDropPermissions;
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
             dragAndDropPermissions = new DragAndDropPermissionsHandler(
                     mData,
                     mUid,
@@ -551,31 +542,30 @@
                     mFlags & DRAG_FLAGS_URI_PERMISSIONS,
                     mSourceUserId,
                     targetUserId);
+        } else {
+            dragAndDropPermissions = null;
         }
         if (mSourceUserId != targetUserId){
             mData.fixUris(mSourceUserId);
         }
         final int myPid = Process.myPid();
         final IBinder token = touchedWin.mClient.asBinder();
-        DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
+        final DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
                 null, null, mData, dragAndDropPermissions, false);
         try {
             touchedWin.mClient.dispatchDragEvent(evt);
 
             // 5 second timeout for this window to respond to the drop
-            mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
-            Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
-            mService.mH.sendMessageDelayed(msg, 5000);
+            mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, token);
         } catch (RemoteException e) {
             Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
-            return true;
+            endDragLocked();
         } finally {
             if (myPid != touchedWin.mSession.mPid) {
                 evt.recycle();
             }
         }
         mToken = token;
-        return false;
     }
 
     private static DragEvent obtainDragEvent(WindowState win, int action,
@@ -641,40 +631,13 @@
         return (mTouchSource & source) == source;
     }
 
-    void overridePointerIconLw(int touchSource) {
+    void overridePointerIconLocked(int touchSource) {
         mTouchSource = touchSource;
         if (isFromSource(InputDevice.SOURCE_MOUSE)) {
             InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
         }
     }
 
-    private class DragStateHandler extends Handler {
-        DragStateHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_ANIMATION_END:
-                    synchronized (mService.mWindowMap) {
-                        if (mService.mDragDropController.mDragState != DragState.this) {
-                            Slog.wtf(TAG_WM, "mDragState is updated unexpectedly while " +
-                                    "playing animation");
-                            return;
-                        }
-                        if (mAnimator == null) {
-                            Slog.wtf(TAG_WM, "Unexpected null mAnimator");
-                            return;
-                        }
-                        mAnimator = null;
-                        cleanUpDragLw();
-                    }
-                    break;
-            }
-        }
-    }
-
     private class AnimationListener
             implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
         @Override
@@ -706,9 +669,10 @@
 
         @Override
         public void onAnimationEnd(Animator animator) {
+            mAnimationCompleted = true;
             // Updating mDragState requires the WM lock so continues it on the out of
             // AnimationThread.
-            mHandler.sendEmptyMessage(MSG_ANIMATION_END);
+            mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null);
         }
     }
 }
diff --git a/com/android/server/wm/InputConsumerImpl.java b/com/android/server/wm/InputConsumerImpl.java
index 36753b7..d40db8c 100644
--- a/com/android/server/wm/InputConsumerImpl.java
+++ b/com/android/server/wm/InputConsumerImpl.java
@@ -16,21 +16,36 @@
 
 package com.android.server.wm;
 
+import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
 import android.view.Display;
 import android.view.InputChannel;
 import android.view.WindowManager;
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputWindowHandle;
 
-class InputConsumerImpl {
+import java.io.PrintWriter;
+
+class InputConsumerImpl implements IBinder.DeathRecipient {
     final WindowManagerService mService;
     final InputChannel mServerChannel, mClientChannel;
     final InputApplicationHandle mApplicationHandle;
     final InputWindowHandle mWindowHandle;
 
-    InputConsumerImpl(WindowManagerService service, String name, InputChannel inputChannel) {
+    final IBinder mToken;
+    final String mName;
+    final int mClientPid;
+    final UserHandle mClientUser;
+
+    InputConsumerImpl(WindowManagerService service, IBinder token, String name,
+            InputChannel inputChannel, int clientPid, UserHandle clientUser) {
         mService = service;
+        mToken = token;
+        mName = name;
+        mClientPid = clientPid;
+        mClientUser = clientUser;
 
         InputChannel[] channels = InputChannel.openInputChannelPair(name);
         mServerChannel = channels[0];
@@ -68,6 +83,26 @@
         mWindowHandle.scaleFactor = 1.0f;
     }
 
+    void linkToDeathRecipient() {
+        if (mToken == null) {
+            return;
+        }
+
+        try {
+            mToken.linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            // Client died, do nothing
+        }
+    }
+
+    void unlinkFromDeathRecipient() {
+        if (mToken == null) {
+            return;
+        }
+
+        mToken.unlinkToDeath(this, 0);
+    }
+
     void layout(int dw, int dh) {
         mWindowHandle.touchableRegion.set(0, 0, dw, dh);
         mWindowHandle.frameLeft = 0;
@@ -86,5 +121,19 @@
         mService.mInputManager.unregisterInputChannel(mServerChannel);
         mClientChannel.dispose();
         mServerChannel.dispose();
+        unlinkFromDeathRecipient();
+    }
+
+    @Override
+    public void binderDied() {
+        synchronized (mService.getWindowManagerLock()) {
+            // Clean up the input consumer
+            mService.mInputMonitor.destroyInputConsumer(mName);
+            unlinkFromDeathRecipient();
+        }
+    }
+
+    void dump(PrintWriter pw, String name, String prefix) {
+        pw.println(prefix + "  name=" + name + " pid=" + mClientPid + " user=" + mClientUser);
     }
 }
diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java
index cf1f171..a766097 100644
--- a/com/android/server/wm/InputMonitor.java
+++ b/com/android/server/wm/InputMonitor.java
@@ -25,6 +25,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
@@ -34,8 +35,11 @@
 import android.app.ActivityManager;
 import android.graphics.Rect;
 import android.os.Debug;
+import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -43,7 +47,6 @@
 import android.view.InputEventReceiver;
 import android.view.KeyEvent;
 import android.view.WindowManager;
-
 import android.view.WindowManagerPolicy;
 
 import com.android.server.input.InputApplicationHandle;
@@ -106,8 +109,9 @@
 
         EventReceiverInputConsumer(WindowManagerService service, InputMonitor monitor,
                                    Looper looper, String name,
-                                   InputEventReceiver.Factory inputEventReceiverFactory) {
-            super(service, name, null);
+                                   InputEventReceiver.Factory inputEventReceiverFactory,
+                                   int clientPid, UserHandle clientUser) {
+            super(service, null /* token */, name, null /* inputChannel */, clientPid, clientUser);
             mInputMonitor = monitor;
             mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
                     mClientChannel, looper);
@@ -129,6 +133,7 @@
 
     private void addInputConsumer(String name, InputConsumerImpl consumer) {
         mInputConsumers.put(name, consumer);
+        consumer.linkToDeathRecipient();
         updateInputWindowsLw(true /* force */);
     }
 
@@ -166,17 +171,20 @@
         }
 
         final EventReceiverInputConsumer consumer = new EventReceiverInputConsumer(mService,
-                this, looper, name, inputEventReceiverFactory);
+                this, looper, name, inputEventReceiverFactory, Process.myPid(),
+                UserHandle.SYSTEM);
         addInputConsumer(name, consumer);
         return consumer;
     }
 
-    void createInputConsumer(String name, InputChannel inputChannel) {
+    void createInputConsumer(IBinder token, String name, InputChannel inputChannel, int clientPid,
+            UserHandle clientUser) {
         if (mInputConsumers.containsKey(name)) {
             throw new IllegalStateException("Existing input consumer found with name: " + name);
         }
 
-        final InputConsumerImpl consumer = new InputConsumerImpl(mService, name, inputChannel);
+        final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
+                inputChannel, clientPid, clientUser);
         switch (name) {
             case INPUT_CONSUMER_WALLPAPER:
                 consumer.mWindowHandle.hasWallpaper = true;
@@ -373,7 +381,7 @@
                 Log.d(TAG_WM, "Inserting drag window");
             }
             final InputWindowHandle dragWindowHandle =
-                    mService.mDragDropController.mDragState.getInputWindowHandle();
+                    mService.mDragDropController.getInputWindowHandleLocked();
             if (dragWindowHandle != null) {
                 addInputWindowHandle(dragWindowHandle);
             } else {
@@ -593,7 +601,7 @@
         if (!inputConsumerKeys.isEmpty()) {
             pw.println(prefix + "InputConsumers:");
             for (String key : inputConsumerKeys) {
-                pw.println(prefix + "  name=" + key);
+                mInputConsumers.get(key).dump(pw, key, prefix);
             }
         }
     }
@@ -690,7 +698,7 @@
             // If there's a drag in progress and 'child' is a potential drop target,
             // make sure it's been told about the drag
             if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
-                mService.mDragDropController.mDragState.sendDragStartedIfNeededLw(w);
+                mService.mDragDropController.sendDragStartedIfNeededLocked(w);
             }
 
             addInputWindowHandle(
diff --git a/com/android/server/wm/PinnedStackController.java b/com/android/server/wm/PinnedStackController.java
index 365366a..d8726bf 100644
--- a/com/android/server/wm/PinnedStackController.java
+++ b/com/android/server/wm/PinnedStackController.java
@@ -149,6 +149,9 @@
         @Override
         public void binderDied() {
             // Clean up the state if the listener dies
+            if (mPinnedStackListener != null) {
+                mPinnedStackListener.asBinder().unlinkToDeath(mPinnedStackListenerDeathHandler, 0);
+            }
             mPinnedStackListener = null;
         }
     }
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index a710441..f541926 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -247,7 +247,7 @@
         if (mService.mDisplayManagerInternal != null) {
             mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
                     displayId, displayInfo);
-            mService.configureDisplayPolicyLocked(dc);
+            dc.configureDisplayPolicy();
 
             // Tap Listeners are supported for:
             // 1. All physical displays (multi-display).
@@ -457,7 +457,7 @@
         try {
             forAllWindows(sRemoveReplacedWindowsConsumer, true /* traverseTopToBottom */);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("removeReplacedWindows");
             if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION removeReplacedWindows");
         }
     }
@@ -599,7 +599,7 @@
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
         }
diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java
index c25b19c..3350fea 100644
--- a/com/android/server/wm/ScreenRotationAnimation.java
+++ b/com/android/server/wm/ScreenRotationAnimation.java
@@ -296,7 +296,7 @@
             setRotationInTransaction(originalRotation);
         } finally {
             if (!inTransaction) {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("ScreenRotationAnimation");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation");
             }
@@ -567,7 +567,7 @@
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
                 if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                         TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -607,7 +607,7 @@
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
                 if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                         TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -629,7 +629,7 @@
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("ScreenRotationAnimation.startAnimation");
                 if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                         TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
diff --git a/com/android/server/wm/Session.java b/com/android/server/wm/Session.java
index 717c577..ad7c300 100644
--- a/com/android/server/wm/Session.java
+++ b/com/android/server/wm/Session.java
@@ -313,7 +313,7 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             return mDragDropController.prepareDrag(
-                    mService, mSurfaceSession, callerPid, callerUid, window, flags, width, height,
+                    mSurfaceSession, callerPid, callerUid, window, flags, width, height,
                     outSurface);
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -324,7 +324,7 @@
     public boolean performDrag(IWindow window, IBinder dragToken,
             int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
             ClipData data) {
-        return mDragDropController.performDrag(mService, window, dragToken, touchSource,
+        return mDragDropController.performDrag(window, dragToken, touchSource,
                 touchX, touchY, thumbCenterX, thumbCenterY, data);
     }
 
@@ -332,7 +332,7 @@
     public void reportDropResult(IWindow window, boolean consumed) {
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDragDropController.reportDropResult(mService, window, consumed);
+            mDragDropController.reportDropResult(window, consumed);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -342,7 +342,7 @@
     public void cancelDragAndDrop(IBinder dragToken) {
         final long ident = Binder.clearCallingIdentity();
         try {
-            mDragDropController.cancelDragAndDrop(mService, dragToken);
+            mDragDropController.cancelDragAndDrop(dragToken);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index aff1bc6..95c1d53 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -225,11 +225,13 @@
         }
     }
 
-    private void getRawBounds(Rect outBounds) {
-        if (mContainer.getRawFullscreen()) {
-            outBounds.setEmpty();
-        } else {
-            mContainer.getRawBounds(outBounds);
+    public void getRawBounds(Rect outBounds) {
+        synchronized (mWindowMap) {
+            if (mContainer.getRawFullscreen()) {
+                outBounds.setEmpty();
+            } else {
+                mContainer.getRawBounds(outBounds);
+            }
         }
     }
 
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 7620cb0..13435d7 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -419,7 +419,7 @@
         return mFillsParent
                 || !inSplitScreenSecondaryWindowingMode()
                 || displayContent == null
-                || displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null;
+                || displayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null;
     }
 
     /** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -678,7 +678,7 @@
                 mChildren.get(i).forceWindowsScaleableInTransaction(force);
             }
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("forceWindowsScaleable");
         }
     }
 
diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java
index 87de151..12f6b5a 100644
--- a/com/android/server/wm/TaskPositioner.java
+++ b/com/android/server/wm/TaskPositioner.java
@@ -16,8 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.RESIZE_MODE_USER;
 import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -210,9 +210,9 @@
 
                         if (mCurrentDimSide != CTRL_NONE) {
                             final int createMode = mCurrentDimSide == CTRL_LEFT
-                                    ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
-                                    : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-                            mService.mActivityManager.moveTaskToDockedStack(
+                                    ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                                    : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+                            mService.mActivityManager.setTaskWindowingModeSplitScreenPrimary(
                                     mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
                                     null /* initialBounds */);
                         }
@@ -637,7 +637,7 @@
         } else {
             showDimLayer();
         }
-        mService.closeSurfaceTransaction();
+        mService.closeSurfaceTransaction("updateDimLayerVisibility");
     }
 
     /**
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 791accf..053fb47 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -16,8 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -294,7 +294,7 @@
         if (mFillsParent
                 || !inSplitScreenSecondaryWindowingMode()
                 || mDisplayContent == null
-                || mDisplayContent.getSplitScreenPrimaryStackStack() != null) {
+                || mDisplayContent.getSplitScreenPrimaryStack() != null) {
             return true;
         }
         return false;
@@ -442,7 +442,7 @@
         mTmpRect2.set(mBounds);
         mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         if (inSplitScreenPrimaryWindowingMode()) {
-            repositionDockedStackAfterRotation(mTmpRect2);
+            repositionPrimarySplitScreenStackAfterRotation(mTmpRect2);
             snapDockedStackAfterRotation(mTmpRect2);
             final int newDockSide = getDockSide(mTmpRect2);
 
@@ -450,8 +450,8 @@
             // might change after a rotation and the original values will be invalid.
             mService.setDockedStackCreateStateLocked(
                     (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
-                            ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
-                            : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
+                            ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                            : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT,
                     null);
             mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
         }
@@ -466,14 +466,14 @@
     }
 
     /**
-     * Some dock sides are not allowed by the policy. This method queries the policy and moves
-     * the docked stack around if needed.
+     * Some primary split screen sides are not allowed by the policy. This method queries the policy
+     * and moves the primary stack around if needed.
      *
-     * @param inOutBounds the bounds of the docked stack to adjust
+     * @param inOutBounds the bounds of the primary stack to adjust
      */
-    private void repositionDockedStackAfterRotation(Rect inOutBounds) {
+    private void repositionPrimarySplitScreenStackAfterRotation(Rect inOutBounds) {
         int dockSide = getDockSide(inOutBounds);
-        if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+        if (mDisplayContent.getDockedDividerController().canPrimaryStackDockTo(dockSide)) {
             return;
         }
         mDisplayContent.getLogicalDisplayRect(mTmpRect);
@@ -523,7 +523,7 @@
         final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
                 mService.mContext.getResources(), displayWidth, displayHeight,
                 dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds,
-                isMinimizedDockAndHomeStackResizable());
+                getDockSide(), isMinimizedDockAndHomeStackResizable());
         final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
 
         // Recalculate the bounds based on the position of the target.
@@ -603,6 +603,14 @@
         } else {
             maxPosition = computeMaxPosition(maxPosition);
         }
+
+        // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid.
+        if (targetPosition == POSITION_BOTTOM && minPosition == 0) {
+            return POSITION_BOTTOM;
+        } else if (targetPosition == POSITION_TOP
+                && maxPosition == (addingNew ? stackSize : stackSize - 1)) {
+            return POSITION_TOP;
+        }
         // Reset position based on minimum/maximum possible positions.
         return Math.min(Math.max(targetPosition, minPosition), maxPosition);
     }
@@ -677,9 +685,12 @@
     public void onConfigurationChanged(Configuration newParentConfig) {
         final int prevWindowingMode = getWindowingMode();
         super.onConfigurationChanged(newParentConfig);
-        if (mDisplayContent != null && prevWindowingMode != getWindowingMode()) {
-            mDisplayContent.onStackWindowingModeChanged(this);
+        final int windowingMode = getWindowingMode();
+        if (mDisplayContent == null || prevWindowingMode == windowingMode) {
+            return;
         }
+        mDisplayContent.onStackWindowingModeChanged(this);
+        updateBoundsForWindowModeChange();
     }
 
     @Override
@@ -691,39 +702,41 @@
         mDisplayContent = dc;
         mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
                 "animation background stackId=" + mStackId);
+        updateBoundsForWindowModeChange();
+        super.onDisplayChanged(dc);
+    }
 
+    private void updateBoundsForWindowModeChange() {
         Rect bounds = null;
-        final TaskStack dockedStack = dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
-        if (inSplitScreenPrimaryWindowingMode()
-                || (dockedStack != null && inSplitScreenSecondaryWindowingMode()
-                        && !dockedStack.fillsParent())) {
+        final boolean inSplitScreenPrimary = inSplitScreenPrimaryWindowingMode();
+        final TaskStack splitScreenStack =
+                mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+        if (inSplitScreenPrimary || (splitScreenStack != null
+                && inSplitScreenSecondaryWindowingMode() && !splitScreenStack.fillsParent())) {
             // The existence of a docked stack affects the size of other static stack created since
             // the docked stack occupies a dedicated region on screen, but only if the dock stack is
             // not fullscreen. If it's fullscreen, it means that we are in the transition of
             // dismissing it, so we must not resize this stack.
             bounds = new Rect();
-            dc.getLogicalDisplayRect(mTmpRect);
+            mDisplayContent.getLogicalDisplayRect(mTmpRect);
             mTmpRect2.setEmpty();
-            if (dockedStack != null) {
-                dockedStack.getRawBounds(mTmpRect2);
+            if (splitScreenStack != null) {
+                splitScreenStack.getRawBounds(mTmpRect2);
             }
             final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
-                    == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+                    == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
             getStackDockedModeBounds(mTmpRect, bounds, mTmpRect2,
-                    mDisplayContent.mDividerControllerLocked.getContentWidth(),
-                    dockedOnTopOrLeft);
+                    mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);
         } else if (inPinnedWindowingMode()) {
             // Update the bounds based on any changes to the display info
             getAnimationOrCurrentBounds(mTmpRect2);
-            boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
-                    mTmpRect2, mTmpRect3);
-            if (updated) {
+            if (mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
+                    mTmpRect2, mTmpRect3)) {
                 bounds = new Rect(mTmpRect3);
             }
         }
 
         updateDisplayInfo(bounds);
-        super.onDisplayChanged(dc);
     }
 
     /**
@@ -773,7 +786,7 @@
         }
 
         final TaskStack dockedStack =
-                mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+                mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
         if (dockedStack == null) {
             // Not sure why you are calling this method when there is no docked stack...
             throw new IllegalStateException(
@@ -914,10 +927,6 @@
             mAnimationBackgroundSurface = null;
         }
 
-        if (inSplitScreenPrimaryWindowingMode()) {
-            mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false);
-        }
-
         mDisplayContent = null;
         mService.mWindowPlacerLocked.requestTraversal();
     }
diff --git a/com/android/server/wm/TaskWindowContainerController.java b/com/android/server/wm/TaskWindowContainerController.java
index 65f8cdf..b3bb0b7 100644
--- a/com/android/server/wm/TaskWindowContainerController.java
+++ b/com/android/server/wm/TaskWindowContainerController.java
@@ -103,6 +103,10 @@
         }
     }
 
+    public void positionChildAtTop(AppWindowContainerController childController) {
+        positionChildAt(childController, POSITION_TOP);
+    }
+
     public void positionChildAt(AppWindowContainerController childController, int position) {
         synchronized(mService.mWindowMap) {
             final AppWindowToken aToken = childController.mContainer;
diff --git a/com/android/server/wm/WindowAnimator.java b/com/android/server/wm/WindowAnimator.java
index e409a68..1912095 100644
--- a/com/android/server/wm/WindowAnimator.java
+++ b/com/android/server/wm/WindowAnimator.java
@@ -233,7 +233,7 @@
             } catch (RuntimeException e) {
                 Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("WindowAnimator");
                 if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
             }
 
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index f31ea67..ce34306 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -20,7 +20,7 @@
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
 import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
-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.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
@@ -80,7 +80,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
@@ -125,7 +124,9 @@
 import android.app.ActivityThread;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
+import android.app.IAssistDataReceiver;
 import android.content.BroadcastReceiver;
+import android.content.ClipData;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -137,7 +138,6 @@
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -160,10 +160,13 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -203,14 +206,12 @@
 import android.view.IWindowSessionCallback;
 import android.view.InputChannel;
 import android.view.InputDevice;
-import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
@@ -226,7 +227,6 @@
 import android.view.inputmethod.InputMethodManagerInternal;
 
 import com.android.internal.R;
-import com.android.internal.app.IAssistScreenshotReceiver;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -245,7 +245,7 @@
 import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.input.InputManagerService;
-import com.android.server.power.BatterySaverPolicy.ServiceType;
+import android.view.DisplayFrames;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
 
@@ -365,6 +365,8 @@
     private static final int TRANSITION_ANIMATION_SCALE = 1;
     private static final int ANIMATION_DURATION_SCALE = 2;
 
+    final WindowTracing mWindowTracing;
+
     final private KeyguardDisableHandler mKeyguardDisableHandler;
     boolean mKeyguardGoingAway;
     // VR Vr2d Display Id.
@@ -560,7 +562,7 @@
     // The root of the device window hierarchy.
     RootWindowContainer mRoot;
 
-    int mDockedStackCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+    int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     Rect mDockedStackCreateBounds;
 
     private final SparseIntArray mTmpTaskIds = new SparseIntArray();
@@ -568,6 +570,8 @@
     boolean mForceResizableTasks = false;
     boolean mSupportsPictureInPicture = false;
 
+    private boolean mDisableTransitionAnimation = false;
+
     int getDragLayerLocked() {
         return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
     }
@@ -751,7 +755,7 @@
     boolean mAllowTheaterModeWakeFromLayout;
 
     TaskPositioner mTaskPositioner;
-    final DragDropController mDragDropController = new DragDropController();
+    final DragDropController mDragDropController;
 
     // For frozen screen animations.
     private int mExitAnimId, mEnterAnimId;
@@ -771,110 +775,6 @@
 
     private WindowContentFrameStats mTempWindowRenderStats;
 
-    final class DragInputEventReceiver extends InputEventReceiver {
-        // Set, if stylus button was down at the start of the drag.
-        private boolean mStylusButtonDownAtStart;
-        // Indicates the first event to check for button state.
-        private boolean mIsStartEvent = true;
-        // Set to true to ignore input events after the drag gesture is complete but the drag events
-        // are still being dispatched.
-        private boolean mMuteInput = false;
-
-        public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
-            super(inputChannel, looper);
-        }
-
-        @Override
-        public void onInputEvent(InputEvent event, int displayId) {
-            boolean handled = false;
-            try {
-                if (mDragDropController.mDragState == null) {
-                    // The drag has ended but the clean-up message has not been processed by
-                    // window manager. Drop events that occur after this until window manager
-                    // has a chance to clean-up the input handle.
-                    handled = true;
-                    return;
-                }
-                if (event instanceof MotionEvent
-                        && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
-                        && !mMuteInput) {
-                    final MotionEvent motionEvent = (MotionEvent)event;
-                    boolean endDrag = false;
-                    final float newX = motionEvent.getRawX();
-                    final float newY = motionEvent.getRawY();
-                    final boolean isStylusButtonDown =
-                            (motionEvent.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
-
-                    if (mIsStartEvent) {
-                        if (isStylusButtonDown) {
-                            // First event and the button was down, check for the button being
-                            // lifted in the future, if that happens we'll drop the item.
-                            mStylusButtonDownAtStart = true;
-                        }
-                        mIsStartEvent = false;
-                    }
-
-                    switch (motionEvent.getAction()) {
-                    case MotionEvent.ACTION_DOWN: {
-                        if (DEBUG_DRAG) {
-                            Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
-                        }
-                    } break;
-
-                    case MotionEvent.ACTION_MOVE: {
-                        if (mStylusButtonDownAtStart && !isStylusButtonDown) {
-                            if (DEBUG_DRAG) Slog.d(TAG_WM, "Button no longer pressed; dropping at "
-                                    + newX + "," + newY);
-                            mMuteInput = true;
-                            synchronized (mWindowMap) {
-                                endDrag = mDragDropController.mDragState.notifyDropLw(newX, newY);
-                            }
-                        } else {
-                            synchronized (mWindowMap) {
-                                // move the surface and tell the involved window(s) where we are
-                                mDragDropController.mDragState.notifyMoveLw(newX, newY);
-                            }
-                        }
-                    } break;
-
-                    case MotionEvent.ACTION_UP: {
-                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Got UP on move channel; dropping at "
-                                + newX + "," + newY);
-                        mMuteInput = true;
-                        synchronized (mWindowMap) {
-                            endDrag = mDragDropController.mDragState.notifyDropLw(newX, newY);
-                        }
-                    } break;
-
-                    case MotionEvent.ACTION_CANCEL: {
-                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
-                        mMuteInput = true;
-                        endDrag = true;
-                    } break;
-                    }
-
-                    if (endDrag) {
-                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ended; tearing down state");
-                        // tell all the windows that the drag has ended
-                        synchronized (mWindowMap) {
-                            // endDragLw will post back to looper to dispose the receiver
-                            // since we still need the receiver for the last finishInputEvent.
-                            mDragDropController.mDragState.endDragLw();
-                        }
-                        mStylusButtonDownAtStart = false;
-                        mIsStartEvent = true;
-                    }
-
-                    handled = true;
-                }
-            } catch (Exception e) {
-                Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
-            } finally {
-                finishInputEvent(event, handled);
-            }
-        }
-    }
-
     /**
      * Whether the UI is currently running in touch mode (not showing
      * navigational focus because the user is directly pressing the screen).
@@ -928,15 +828,20 @@
 
     /**
      * Closes a surface transaction.
+     * @param where debug string indicating where the transaction originated
      */
-    void closeSurfaceTransaction() {
+    void closeSurfaceTransaction(String where) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
             synchronized (mWindowMap) {
-                if (mRoot.mSurfaceTraceEnabled) {
-                    mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+                try {
+                    traceStateLocked(where);
+                } finally {
+                    if (mRoot.mSurfaceTraceEnabled) {
+                        mRoot.mRemoteEventTrace.closeSurfaceTransaction();
+                    }
+                    SurfaceControl.closeTransaction();
                 }
-                SurfaceControl.closeTransaction();
             }
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -1037,6 +942,12 @@
         }, 0);
     }
 
+    @Override
+    public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, ShellCallback callback, ResultReceiver result) {
+        new WindowManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
+    }
+
     private WindowManagerService(Context context, InputManagerService inputManager,
             boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
             WindowManagerPolicy policy) {
@@ -1058,6 +969,8 @@
                 com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
         mMaxUiWidth = context.getResources().getInteger(
                 com.android.internal.R.integer.config_maxUiWidth);
+        mDisableTransitionAnimation = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_disableTransitionAnimation);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
         mDisplaySettings = new DisplaySettings();
@@ -1067,6 +980,8 @@
         mPolicy = policy;
         mTaskSnapshotController = new TaskSnapshotController(this);
 
+        mWindowTracing = WindowTracing.createDefaultAndStartLooper(context);
+
         LocalServices.addService(WindowManagerPolicy.class, mPolicy);
 
         if(mInputManager != null) {
@@ -1164,6 +1079,7 @@
         mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
 
+        mDragDropController = new DragDropController(this, mH.getLooper());
 
         LocalServices.addService(WindowManagerInternal.class, new LocalService());
         initPolicy();
@@ -1175,7 +1091,7 @@
         try {
             createWatermarkInTransaction();
         } finally {
-            closeSurfaceTransaction();
+            closeSurfaceTransaction("createWatermarkInTransaction");
         }
 
         showEmulatorDisplayOverlayIfNeeded();
@@ -1410,7 +1326,10 @@
                 return WindowManagerGlobal.ADD_INVALID_DISPLAY;
             }
 
-            mPolicy.adjustWindowParamsLw(win.mAttrs);
+            final boolean hasStatusBarServicePermission =
+                    mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
+                            == PackageManager.PERMISSION_GRANTED;
+            mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
             win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
 
             res = mPolicy.prepareAddWindowLw(win, attrs);
@@ -1534,23 +1453,19 @@
                 prepareNoneTransitionForRelaunching(atoken);
             }
 
-            if (displayContent.isDefaultDisplay) {
-                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
-                final Rect taskBounds;
-                if (atoken != null && atoken.getTask() != null) {
-                    taskBounds = mTmpRect;
-                    atoken.getTask().getBounds(mTmpRect);
-                } else {
-                    taskBounds = null;
-                }
-                if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, displayInfo.rotation,
-                        displayInfo.logicalWidth, displayInfo.logicalHeight, outContentInsets,
-                        outStableInsets, outOutsets)) {
-                    res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
-                }
+            final DisplayFrames displayFrames = displayContent.mDisplayFrames;
+            // TODO: Not sure if onDisplayInfoUpdated() call is needed.
+            displayFrames.onDisplayInfoUpdated(displayContent.getDisplayInfo());
+            final Rect taskBounds;
+            if (atoken != null && atoken.getTask() != null) {
+                taskBounds = mTmpRect;
+                atoken.getTask().getBounds(mTmpRect);
             } else {
-                outContentInsets.setEmpty();
-                outStableInsets.setEmpty();
+                taskBounds = null;
+            }
+            if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, displayFrames, outContentInsets,
+                    outStableInsets, outOutsets)) {
+                res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
             }
 
             if (mInTouchMode) {
@@ -1936,8 +1851,11 @@
             MergedConfiguration mergedConfiguration, Surface outSurface) {
         int result = 0;
         boolean configChanged;
-        boolean hasStatusBarPermission =
-                mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+        final boolean hasStatusBarPermission =
+                mContext.checkCallingOrSelfPermission(permission.STATUS_BAR)
+                        == PackageManager.PERMISSION_GRANTED;
+        final boolean hasStatusBarServicePermission =
+                mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
                         == PackageManager.PERMISSION_GRANTED;
 
         long origId = Binder.clearCallingIdentity();
@@ -1957,7 +1875,7 @@
             int attrChanges = 0;
             int flagChanges = 0;
             if (attrs != null) {
-                mPolicy.adjustWindowParamsLw(attrs);
+                mPolicy.adjustWindowParamsLw(win, attrs, hasStatusBarServicePermission);
                 // if they don't have the permission, mask out the status bar bits
                 if (seq == win.mSeq) {
                     int systemUiVisibility = attrs.systemUiVisibility
@@ -2362,6 +2280,14 @@
 
     boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
             int transit, boolean enter, boolean isVoiceInteraction) {
+        if (mDisableTransitionAnimation) {
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                Slog.v(TAG_WM,
+                        "applyAnimation: transition animation is disabled. atoken=" + atoken);
+            }
+            atoken.mAppAnimator.clearAnimation();
+            return false;
+        }
         // Only apply an animation if the display isn't frozen.  If it is
         // frozen, there is no reason to animate and it can cause strange
         // artifacts when we unfreeze the display if some different animation
@@ -3000,10 +2926,9 @@
     }
 
     public void setKeyguardGoingAway(boolean keyguardGoingAway) {
-// TODO: Use of this can be removed. Revert ag/I8369723d6a77f2c602f1ef080371fa7cd9ee094e
-//        synchronized (mWindowMap) {
-//            mKeyguardGoingAway = keyguardGoingAway;
-//        }
+        synchronized (mWindowMap) {
+            mKeyguardGoingAway = keyguardGoingAway;
+        }
     }
 
     // -------------------------------------------------------------
@@ -3391,7 +3316,7 @@
             // Notify whether the docked stack exists for the current user
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
             final TaskStack stack =
-                    displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
+                    displayContent.getSplitScreenPrimaryStackIgnoringVisibility();
             displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
                     stack != null && stack.hasTaskForUser(newUserId));
 
@@ -3679,7 +3604,7 @@
                     mCircularDisplayMask = null;
                 }
             } finally {
-                closeSurfaceTransaction();
+                closeSurfaceTransaction("showCircularMask");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                         "<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")");
             }
@@ -3704,7 +3629,7 @@
                 }
                 mEmulatorDisplayOverlay.setVisibility(true);
             } finally {
-                closeSurfaceTransaction();
+                closeSurfaceTransaction("showEmulatorDisplayOverlay");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                         "<<< CLOSE TRANSACTION showEmulatorDisplayOverlay");
             }
@@ -3783,7 +3708,7 @@
      * of the target image.
      */
     @Override
-    public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) {
+    public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
         if (!checkCallingPermission(READ_FRAME_BUFFER,
                 "requestAssistScreenshot()")) {
             throw new SecurityException("Requires READ_FRAME_BUFFER permission");
@@ -3795,7 +3720,7 @@
                     1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
                     false /* includeDecor */);
             try {
-                receiver.send(bm);
+                receiver.onHandleAssistScreenshot(bm);
             } catch (RemoteException e) {
             }
         });
@@ -4740,7 +4665,7 @@
         synchronized(mWindowMap) {
             mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_TOUCHSCREEN);
-            configureDisplayPolicyLocked(getDefaultDisplayContentLocked());
+            getDefaultDisplayContentLocked().configureDisplayPolicy();
         }
 
         try {
@@ -4796,8 +4721,6 @@
         public static final int APP_FREEZE_TIMEOUT = 17;
         public static final int SEND_NEW_CONFIGURATION = 18;
         public static final int REPORT_WINDOWS_CHANGE = 19;
-        public static final int DRAG_START_TIMEOUT = 20;
-        public static final int DRAG_END_TIMEOUT = 21;
 
         public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
         public static final int BOOT_TIMEOUT = 23;
@@ -4824,8 +4747,6 @@
 
         public static final int UPDATE_DOCKED_STACK_DIVIDER = 41;
 
-        public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44;
-
         public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
 
         public static final int NOTIFY_APP_TRANSITION_STARTING = 47;
@@ -5053,13 +4974,6 @@
                     break;
                 }
 
-                case DRAG_START_TIMEOUT:
-                case DRAG_END_TIMEOUT:
-                case TEAR_DOWN_DRAG_AND_DROP_INPUT: {
-                    mDragDropController.handleMessage(WindowManagerService.this, msg);
-                    break;
-                }
-
                 case REPORT_HARD_KEYBOARD_STATUS_CHANGE: {
                     notifyHardKeyboardStatusChange();
                     break;
@@ -5608,7 +5522,7 @@
         if (!mDisplayReady) {
             return;
         }
-        configureDisplayPolicyLocked(displayContent);
+        displayContent.configureDisplayPolicy();
         displayContent.setLayoutNeeded();
 
         final int displayId = displayContent.getDisplayId();
@@ -5629,18 +5543,6 @@
         mWindowPlacerLocked.performSurfacePlacement();
     }
 
-    void configureDisplayPolicyLocked(DisplayContent displayContent) {
-        mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
-                displayContent.mBaseDisplayWidth,
-                displayContent.mBaseDisplayHeight,
-                displayContent.mBaseDisplayDensity);
-
-        DisplayInfo displayInfo = displayContent.getDisplayInfo();
-        mPolicy.setDisplayOverscan(displayContent.getDisplay(),
-                displayInfo.overscanLeft, displayInfo.overscanTop,
-                displayInfo.overscanRight, displayInfo.overscanBottom);
-    }
-
     /**
      * Get an array with display ids ordered by focus priority - last items should be given
      * focus first. Sparse array just maps position to displayId.
@@ -6253,9 +6155,10 @@
     }
 
     @Override
-    public void createInputConsumer(String name, InputChannel inputChannel) {
+    public void createInputConsumer(IBinder token, String name, InputChannel inputChannel) {
         synchronized (mWindowMap) {
-            mInputMonitor.createInputConsumer(name, inputChannel);
+            mInputMonitor.createInputConsumer(token, name, inputChannel, Binder.getCallingPid(),
+                    Binder.getCallingUserHandle());
         }
     }
 
@@ -6437,7 +6340,7 @@
      * @param proto     Stream to write the WindowContainer object to.
      * @param trim      If true, reduce the amount of data written.
      */
-    private void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
+    void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
         mPolicy.writeToProto(proto, POLICY);
         mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim);
         if (mCurrentFocus != null) {
@@ -6456,6 +6359,17 @@
         mAppTransition.writeToProto(proto, APP_TRANSITION);
     }
 
+    void traceStateLocked(String where) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
+        try {
+            mWindowTracing.traceStateLocked(where, this);
+        } catch (Exception e) {
+            Log.wtf(TAG, "Exception while tracing state", e);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+    }
+
     private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
             ArrayList<WindowState> windows) {
         pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
@@ -6946,11 +6860,16 @@
     public void setWillReplaceWindow(IBinder token, boolean animate) {
         synchronized (mWindowMap) {
             final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
-            if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
+            if (appWindowToken == null) {
                 Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
                         + token);
                 return;
             }
+            if (!appWindowToken.hasContentToDisplay()) {
+                Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content"
+                        + token);
+                return;
+            }
             appWindowToken.setWillReplaceWindows(animate);
         }
     }
@@ -6970,11 +6889,16 @@
     void setWillReplaceWindows(IBinder token, boolean childrenOnly) {
         synchronized (mWindowMap) {
             final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
-            if (appWindowToken == null || !appWindowToken.hasContentToDisplay()) {
+            if (appWindowToken == null) {
                 Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
                         + token);
                 return;
             }
+            if (!appWindowToken.hasContentToDisplay()) {
+                Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content"
+                        + token);
+                return;
+            }
 
             if (childrenOnly) {
                 appWindowToken.setWillReplaceChildWindows();
@@ -7024,7 +6948,7 @@
     public int getDockedStackSide() {
         synchronized (mWindowMap) {
             final TaskStack dockedStack = getDefaultDisplayContentLocked()
-                    .getSplitScreenPrimaryStackStackIgnoringVisibility();
+                    .getSplitScreenPrimaryStackIgnoringVisibility();
             return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
         }
     }
@@ -7170,7 +7094,7 @@
         }
 
         synchronized (mWindowMap) {
-            if (mDragDropController.mDragState != null) {
+            if (mDragDropController.dragDropActiveLocked()) {
                 // Drag cursor overrides the app cursor.
                 return;
             }
@@ -7361,11 +7285,6 @@
         }
 
         @Override
-        public boolean isKeyguardGoingAway() {
-            return WindowManagerService.this.mKeyguardGoingAway;
-        }
-
-        @Override
         public boolean isKeyguardShowingAndNotOccluded() {
             return WindowManagerService.this.isKeyguardShowingAndNotOccluded();
         }
@@ -7446,7 +7365,9 @@
         @Override
         public int getInputMethodWindowVisibleHeight() {
             synchronized (mWindowMap) {
-                return mPolicy.getInputMethodWindowVisibleHeightLw();
+                // TODO(multi-display): Have caller pass in the display they are interested in.
+                final DisplayContent dc = getDefaultDisplayContentLocked();
+                return dc.mDisplayFrames.getInputMethodWindowVisibleHeight();
             }
         }
 
@@ -7530,6 +7451,11 @@
                 mVr2dDisplayId = vr2dDisplayId;
             }
         }
+
+        @Override
+        public void registerDragDropControllerCallback(IDragDropCallback callback) {
+            mDragDropController.registerCallback(callback);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/com/android/server/wm/WindowManagerShellCommand.java b/com/android/server/wm/WindowManagerShellCommand.java
new file mode 100644
index 0000000..4b98d9d
--- /dev/null
+++ b/com/android/server/wm/WindowManagerShellCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+/**
+ * ShellCommands for WindowManagerService.
+ *
+ * Use with {@code adb shell cmd window ...}.
+ */
+public class WindowManagerShellCommand extends ShellCommand {
+
+    private final WindowManagerService mService;
+
+    public WindowManagerShellCommand(WindowManagerService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        switch (cmd) {
+            case "tracing":
+                return mService.mWindowTracing.onShellCommand(this, getNextArgRequired());
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("Window Manager (window) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  tracing (start | stop)");
+        pw.println("    start or stop window tracing");
+        pw.println();
+    }
+}
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 4370a76..6b1932d 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -1849,110 +1849,109 @@
 
         final long origId = Binder.clearCallingIdentity();
 
-        disposeInputChannel();
+        try {
+            disposeInputChannel();
 
-        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
-                + ": mSurfaceController=" + mWinAnimator.mSurfaceController
-                + " mAnimatingExit=" + mAnimatingExit
-                + " mRemoveOnExit=" + mRemoveOnExit
-                + " mHasSurface=" + mHasSurface
-                + " surfaceShowing=" + mWinAnimator.getShown()
-                + " isAnimationSet=" + mWinAnimator.isAnimationSet()
-                + " app-animation="
-                + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
-                + " mWillReplaceWindow=" + mWillReplaceWindow
-                + " inPendingTransaction="
-                + (mAppToken != null ? mAppToken.inPendingTransaction : false)
-                + " mDisplayFrozen=" + mService.mDisplayFrozen
-                + " callers=" + Debug.getCallers(6));
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM, "Remove " + this
+                    + ": mSurfaceController=" + mWinAnimator.mSurfaceController
+                    + " mAnimatingExit=" + mAnimatingExit
+                    + " mRemoveOnExit=" + mRemoveOnExit
+                    + " mHasSurface=" + mHasSurface
+                    + " surfaceShowing=" + mWinAnimator.getShown()
+                    + " isAnimationSet=" + mWinAnimator.isAnimationSet()
+                    + " app-animation="
+                    + (mAppToken != null ? mAppToken.mAppAnimator.animation : null)
+                    + " mWillReplaceWindow=" + mWillReplaceWindow
+                    + " inPendingTransaction="
+                    + (mAppToken != null ? mAppToken.inPendingTransaction : false)
+                    + " mDisplayFrozen=" + mService.mDisplayFrozen
+                    + " callers=" + Debug.getCallers(6));
 
-        // Visibility of the removed window. Will be used later to update orientation later on.
-        boolean wasVisible = false;
+            // Visibility of the removed window. Will be used later to update orientation later on.
+            boolean wasVisible = false;
 
-        final int displayId = getDisplayId();
+            final int displayId = getDisplayId();
 
-        // First, see if we need to run an animation. If we do, we have to hold off on removing the
-        // window until the animation is done. If the display is frozen, just remove immediately,
-        // since the animation wouldn't be seen.
-        if (mHasSurface && mToken.okToAnimate()) {
-            if (mWillReplaceWindow) {
-                // This window is going to be replaced. We need to keep it around until the new one
-                // gets added, then we will get rid of this one.
-                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                        "Preserving " + this + " until the new one is " + "added");
-                // TODO: We are overloading mAnimatingExit flag to prevent the window state from
-                // been removed. We probably need another flag to indicate that window removal
-                // should be deffered vs. overloading the flag that says we are playing an exit
-                // animation.
-                mAnimatingExit = true;
-                mReplacingRemoveRequested = true;
-                Binder.restoreCallingIdentity(origId);
-                return;
-            }
-
-            // If we are not currently running the exit animation, we need to see about starting one
-            wasVisible = isWinVisibleLw();
-
-            if (keepVisibleDeadWindow) {
-                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                        "Not removing " + this + " because app died while it's visible");
-
-                mAppDied = true;
-                setDisplayLayoutNeeded();
-                mService.mWindowPlacerLocked.performSurfacePlacement();
-
-                // Set up a replacement input channel since the app is now dead.
-                // We need to catch tapping on the dead window to restart the app.
-                openInputChannel(null);
-                mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
-
-                Binder.restoreCallingIdentity(origId);
-                return;
-            }
-
-            if (wasVisible) {
-                final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
-
-                // Try starting an animation.
-                if (mWinAnimator.applyAnimationLocked(transit, false)) {
+            // First, see if we need to run an animation. If we do, we have to hold off on removing the
+            // window until the animation is done. If the display is frozen, just remove immediately,
+            // since the animation wouldn't be seen.
+            if (mHasSurface && mToken.okToAnimate()) {
+                if (mWillReplaceWindow) {
+                    // This window is going to be replaced. We need to keep it around until the new one
+                    // gets added, then we will get rid of this one.
+                    if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                            "Preserving " + this + " until the new one is " + "added");
+                    // TODO: We are overloading mAnimatingExit flag to prevent the window state from
+                    // been removed. We probably need another flag to indicate that window removal
+                    // should be deffered vs. overloading the flag that says we are playing an exit
+                    // animation.
                     mAnimatingExit = true;
+                    mReplacingRemoveRequested = true;
+                    return;
                 }
-                //TODO (multidisplay): Magnification is supported only for the default display.
-                if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
-                    mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
-                }
-            }
-            final boolean isAnimating =
-                    mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
-            final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
-                    && mAppToken.isLastWindow(this);
-            // We delay the removal of a window if it has a showing surface that can be used to run
-            // exit animation and it is marked as exiting.
-            // Also, If isn't the an animating starting window that is the last window in the app.
-            // We allow the removal of the non-animating starting window now as there is no
-            // additional window or animation that will trigger its removal.
-            if (mWinAnimator.getShown() && mAnimatingExit
-                    && (!lastWindowIsStartingWindow || isAnimating)) {
-                // The exit animation is running or should run... wait for it!
-                if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                        "Not removing " + this + " due to exit animation ");
-                setupWindowForRemoveOnExit();
-                if (mAppToken != null) {
-                    mAppToken.updateReportedVisibilityLocked();
-                }
-                Binder.restoreCallingIdentity(origId);
-                return;
-            }
-        }
 
-        removeImmediately();
-        // Removing a visible window will effect the computed orientation
-        // So just update orientation if needed.
-        if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
-            mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+                // If we are not currently running the exit animation, we need to see about starting one
+                wasVisible = isWinVisibleLw();
+
+                if (keepVisibleDeadWindow) {
+                    if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                            "Not removing " + this + " because app died while it's visible");
+
+                    mAppDied = true;
+                    setDisplayLayoutNeeded();
+                    mService.mWindowPlacerLocked.performSurfacePlacement();
+
+                    // Set up a replacement input channel since the app is now dead.
+                    // We need to catch tapping on the dead window to restart the app.
+                    openInputChannel(null);
+                    mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+                    return;
+                }
+
+                if (wasVisible) {
+                    final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;
+
+                    // Try starting an animation.
+                    if (mWinAnimator.applyAnimationLocked(transit, false)) {
+                        mAnimatingExit = true;
+                    }
+                    //TODO (multidisplay): Magnification is supported only for the default display.
+                    if (mService.mAccessibilityController != null && displayId == DEFAULT_DISPLAY) {
+                        mService.mAccessibilityController.onWindowTransitionLocked(this, transit);
+                    }
+                }
+                final boolean isAnimating =
+                        mWinAnimator.isAnimationSet() && !mWinAnimator.isDummyAnimation();
+                final boolean lastWindowIsStartingWindow = startingWindow && mAppToken != null
+                        && mAppToken.isLastWindow(this);
+                // We delay the removal of a window if it has a showing surface that can be used to run
+                // exit animation and it is marked as exiting.
+                // Also, If isn't the an animating starting window that is the last window in the app.
+                // We allow the removal of the non-animating starting window now as there is no
+                // additional window or animation that will trigger its removal.
+                if (mWinAnimator.getShown() && mAnimatingExit
+                        && (!lastWindowIsStartingWindow || isAnimating)) {
+                    // The exit animation is running or should run... wait for it!
+                    if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
+                            "Not removing " + this + " due to exit animation ");
+                    setupWindowForRemoveOnExit();
+                    if (mAppToken != null) {
+                        mAppToken.updateReportedVisibilityLocked();
+                    }
+                    return;
+                }
+            }
+
+            removeImmediately();
+            // Removing a visible window will effect the computed orientation
+            // So just update orientation if needed.
+            if (wasVisible && mService.updateOrientationFromAppTokensLocked(false, displayId)) {
+                mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+            }
+            mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
         }
-        mService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
-        Binder.restoreCallingIdentity(origId);
     }
 
     private void setupWindowForRemoveOnExit() {
@@ -2361,7 +2360,7 @@
                             // also reset drag resizing state, because the owner can't do it
                             // anymore.
                             final TaskStack stack =
-                                    dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
+                                    dc.getSplitScreenPrimaryStackIgnoringVisibility();
                             if (stack != null) {
                                 stack.resetDockedStackToMiddle();
                             }
diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java
index 5266903..86397ae 100644
--- a/com/android/server/wm/WindowStateAnimator.java
+++ b/com/android/server/wm/WindowStateAnimator.java
@@ -732,7 +732,7 @@
             mSurfaceController.setLayerStackInTransaction(getLayerStack());
             mSurfaceController.setLayer(mAnimLayer);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("createSurfaceLocked");
         }
 
         mLastHidden = true;
@@ -1711,7 +1711,7 @@
             Slog.w(TAG, "Error positioning surface of " + mWin
                     + " pos=(" + left + "," + top + ")", e);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("setWallpaperOffset");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION setWallpaperOffset");
         }
diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java
index edd650a..a214523 100644
--- a/com/android/server/wm/WindowSurfaceController.java
+++ b/com/android/server/wm/WindowSurfaceController.java
@@ -259,7 +259,7 @@
                     mSurfaceControl.setLayer(layer);
                 }
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("setLayer");
             }
         }
     }
@@ -385,7 +385,7 @@
         try {
             mSurfaceControl.setTransparentRegionHint(region);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("setTransparentRegion");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION setTransparentRegion");
         }
@@ -403,7 +403,7 @@
         try {
             mSurfaceControl.setOpaque(isOpaque);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("setOpaqueLocked");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
         }
     }
@@ -420,7 +420,7 @@
         try {
             mSurfaceControl.setSecure(isSecure);
         } finally {
-            mService.closeSurfaceTransaction();
+            mService.closeSurfaceTransaction("setSecure");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
         }
     }
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index d57fdd2..cd5e475 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -429,7 +429,7 @@
             try {
                 mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
             } finally {
-                mService.closeSurfaceTransaction();
+                mService.closeSurfaceTransaction("handleAppTransitionReadyLocked");
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                         "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
             }
diff --git a/com/android/server/wm/WindowTracing.java b/com/android/server/wm/WindowTracing.java
new file mode 100644
index 0000000..5657f6c
--- /dev/null
+++ b/com/android/server/wm/WindowTracing.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.proto.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WHERE;
+import static com.android.server.wm.proto.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
+
+import android.content.Context;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * A class that allows window manager to dump its state continuously to a trace file, such that a
+ * time series of window manager state can be analyzed after the fact.
+ */
+class WindowTracing {
+
+    private static final String TAG = "WindowTracing";
+    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+    private final Object mLock = new Object();
+    private final File mTraceFile;
+    private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200);
+
+    private boolean mEnabled;
+    private volatile boolean mEnabledLockFree;
+
+    WindowTracing(File file) {
+        mTraceFile = file;
+    }
+
+    void startTrace(PrintWriter pw) throws IOException {
+        synchronized (mLock) {
+            logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+            mWriteQueue.clear();
+            mTraceFile.delete();
+            try (OutputStream os = new FileOutputStream(mTraceFile)) {
+                ProtoOutputStream proto = new ProtoOutputStream(os);
+                proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+                proto.flush();
+            }
+            mEnabled = mEnabledLockFree = true;
+        }
+    }
+
+    private void logAndPrintln(PrintWriter pw, String msg) {
+        Log.i(TAG, msg);
+        pw.println(msg);
+        pw.flush();
+    }
+
+    void stopTrace(PrintWriter pw) {
+        synchronized (mLock) {
+            logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+            mEnabled = mEnabledLockFree = false;
+            while (!mWriteQueue.isEmpty()) {
+                if (mEnabled) {
+                    logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
+                    throw new IllegalStateException("tracing enabled while waiting for flush.");
+                }
+                try {
+                    mLock.wait();
+                    mLock.notify();
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+            logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+        }
+    }
+
+    void appendTraceEntry(ProtoOutputStream proto) {
+        if (!mEnabledLockFree) {
+            return;
+        }
+
+        if (!mWriteQueue.offer(proto)) {
+            Log.e(TAG, "Dropping window trace entry, queue full");
+        }
+    }
+
+    void loop() {
+        for (;;) {
+            loopOnce();
+        }
+    }
+
+    @VisibleForTesting
+    void loopOnce() {
+        ProtoOutputStream proto;
+        try {
+            proto = mWriteQueue.take();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            return;
+        }
+
+        synchronized (mLock) {
+            try {
+                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
+                try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) {
+                    os.write(proto.getBytes());
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to write file " + mTraceFile, e);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+            }
+            mLock.notify();
+        }
+    }
+
+    boolean isEnabled() {
+        return mEnabledLockFree;
+    }
+
+    static WindowTracing createDefaultAndStartLooper(Context context) {
+        File file = new File("/data/system/window_trace.proto");
+        WindowTracing windowTracing = new WindowTracing(file);
+        new Thread(windowTracing::loop, "window_tracing").start();
+        return windowTracing;
+    }
+
+    int onShellCommand(ShellCommand shell, String cmd) {
+        PrintWriter pw = shell.getOutPrintWriter();
+        try {
+            switch (cmd) {
+                case "start":
+                    startTrace(pw);
+                    return 0;
+                case "stop":
+                    stopTrace(pw);
+                    return 0;
+                default:
+                    pw.println("Unknown command: " + cmd);
+                    return -1;
+            }
+        } catch (IOException e) {
+            logAndPrintln(pw, e.toString());
+            throw new RuntimeException(e);
+        }
+    }
+
+    void traceStateLocked(String where, WindowManagerService service) {
+        if (!isEnabled()) {
+            return;
+        }
+        ProtoOutputStream os = new ProtoOutputStream();
+        long tokenOuter = os.start(ENTRY);
+        os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+        os.write(WHERE, where);
+
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
+        try {
+            long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+            service.writeToProtoLocked(os, true /* trim */);
+            os.end(tokenInner);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        }
+        os.end(tokenOuter);
+        appendTraceEntry(os);
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+    }
+}
diff --git a/com/android/settingslib/CustomEditTextPreference.java b/com/android/settingslib/CustomEditTextPreference.java
index 90124f1..8e29e50 100644
--- a/com/android/settingslib/CustomEditTextPreference.java
+++ b/com/android/settingslib/CustomEditTextPreference.java
@@ -49,8 +49,13 @@
     }
 
     public EditText getEditText() {
-        return mFragment != null ? (EditText) mFragment.getDialog().findViewById(android.R.id.edit)
-                : null;
+        if (mFragment != null) {
+            final Dialog dialog = mFragment.getDialog();
+            if (dialog != null) {
+                return (EditText) dialog.findViewById(android.R.id.edit);
+            }
+        }
+        return null;
     }
 
     public boolean isDialogOpen() {
diff --git a/com/android/settingslib/DeviceInfoUtils.java b/com/android/settingslib/DeviceInfoUtils.java
index 78a9064..f2cd103 100644
--- a/com/android/settingslib/DeviceInfoUtils.java
+++ b/com/android/settingslib/DeviceInfoUtils.java
@@ -22,12 +22,15 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Build;
+import android.system.Os;
+import android.system.StructUtsname;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.Log;
+import android.support.annotation.VisibleForTesting;
 
 import java.io.BufferedReader;
 import java.io.FileReader;
@@ -45,7 +48,6 @@
 public class DeviceInfoUtils {
     private static final String TAG = "DeviceInfoUtils";
 
-    private static final String FILENAME_PROC_VERSION = "/proc/version";
     private static final String FILENAME_MSV = "/sys/board_properties/soc/msv";
 
     /**
@@ -63,43 +65,36 @@
         }
     }
 
-    public static String getFormattedKernelVersion() {
-        try {
-            return formatKernelVersion(readLine(FILENAME_PROC_VERSION));
-        } catch (IOException e) {
-            Log.e(TAG, "IO Exception when getting kernel version for Device Info screen",
-                    e);
-
-            return "Unavailable";
-        }
+    public static String getFormattedKernelVersion(Context context) {
+            return formatKernelVersion(context, Os.uname());
     }
 
-    public static String formatKernelVersion(String rawKernelVersion) {
-        // Example (see tests for more):
-        // Linux version 4.9.29-g958411d (android-build@xyz) (Android clang version 3.8.256229 \
-        //       (based on LLVM 3.8.256229)) #1 SMP PREEMPT Wed Jun 7 00:06:03 CST 2017
-        // Linux version 4.9.29-geb63318482a7 (android-build@xyz) (gcc version 4.9.x 20150123 \
-        //       (prerelease) (GCC) ) #1 SMP PREEMPT Thu Jun 1 03:41:57 UTC 2017
-        final String PROC_VERSION_REGEX =
-                "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */
-                "\\((\\S+?)\\) " +        /* group 2: "(x@y.com) " */
-                "\\((.+?)\\) " +          /* group 3:  kernel toolchain version information */
-                "(#\\d+) " +              /* group 4: "#1" */
-                "(?:.*?)?" +              /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
-                "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 5: "Thu Jun 28 11:02:39 PDT 2012" */
-
-        Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion);
-        if (!m.matches()) {
-            Log.e(TAG, "Regex did not match on /proc/version: " + rawKernelVersion);
-            return "Unavailable";
-        } else if (m.groupCount() < 4) {
-            Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount()
-                    + " groups");
-            return "Unavailable";
+    @VisibleForTesting
+    static String formatKernelVersion(Context context, StructUtsname uname) {
+        if (uname == null) {
+            return context.getString(R.string.status_unavailable);
         }
-        return m.group(1) + " ("+ m.group(3) + ")\n" +   // 3.0.31-g6fb96c9 (toolchain version)
-                m.group(2) + " " + m.group(4) + "\n" +   // x@y.com #1
-                m.group(5);                              // Thu Jun 28 11:02:39 PDT 2012
+        // Example:
+        // 4.9.29-g958411d
+        // #1 SMP PREEMPT Wed Jun 7 00:06:03 CST 2017
+        final String VERSION_REGEX =
+                "(#\\d+) " +              /* group 1: "#1" */
+                "(?:.*?)?" +              /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */
+                "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 2: "Thu Jun 28 11:02:39 PDT 2012" */
+        Matcher m = Pattern.compile(VERSION_REGEX).matcher(uname.version);
+        if (!m.matches()) {
+            Log.e(TAG, "Regex did not match on uname version " + uname.version);
+            return context.getString(R.string.status_unavailable);
+        }
+
+        // Example output:
+        // 4.9.29-g958411d
+        // #1 Wed Jun 7 00:06:03 CST 2017
+        return new StringBuilder().append(uname.release)
+                .append("\n")
+                .append(m.group(1))
+                .append(" ")
+                .append(m.group(2)).toString();
     }
 
     /**
diff --git a/com/android/settingslib/RestrictedLockUtils.java b/com/android/settingslib/RestrictedLockUtils.java
index 32e6389..c3a36e9 100644
--- a/com/android/settingslib/RestrictedLockUtils.java
+++ b/com/android/settingslib/RestrictedLockUtils.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.graphics.drawable.Drawable;
 import android.os.RemoteException;
@@ -343,7 +344,8 @@
         }
         DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
-        if (dpm == null) {
+        PackageManager pm = context.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) || dpm == null) {
             return null;
         }
         boolean isAccountTypeDisabled = false;
diff --git a/com/android/settingslib/TwoTargetPreference.java b/com/android/settingslib/TwoTargetPreference.java
index 1c161df..8b39f60 100644
--- a/com/android/settingslib/TwoTargetPreference.java
+++ b/com/android/settingslib/TwoTargetPreference.java
@@ -21,41 +21,56 @@
 import android.support.v7.preference.PreferenceViewHolder;
 import android.util.AttributeSet;
 import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
 
 public class TwoTargetPreference extends Preference {
 
+    private boolean mUseSmallIcon;
+    private int mSmallIconSize;
+
     public TwoTargetPreference(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init();
+        init(context);
     }
 
     public TwoTargetPreference(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init();
+        init(context);
     }
 
     public TwoTargetPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
+        init(context);
     }
 
     public TwoTargetPreference(Context context) {
         super(context);
-        init();
+        init(context);
     }
 
-    private void init() {
+    private void init(Context context) {
         setLayoutResource(R.layout.preference_two_target);
+        mSmallIconSize = context.getResources().getDimensionPixelSize(
+                R.dimen.two_target_pref_small_icon_size);
         final int secondTargetResId = getSecondTargetResId();
         if (secondTargetResId != 0) {
             setWidgetLayoutResource(secondTargetResId);
         }
     }
 
+    public void setUseSmallIcon(boolean useSmallIcon) {
+        mUseSmallIcon = useSmallIcon;
+    }
+
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
+        if (mUseSmallIcon) {
+            ImageView icon = holder.itemView.findViewById(android.R.id.icon);
+            icon.setLayoutParams(new LinearLayout.LayoutParams(mSmallIconSize, mSmallIconSize));
+        }
         final View divider = holder.findViewById(R.id.two_target_divider);
         final View widgetFrame = holder.findViewById(android.R.id.widget_frame);
         final boolean shouldHideSecondTarget = shouldHideSecondTarget();
diff --git a/com/android/settingslib/Utils.java b/com/android/settingslib/Utils.java
index d90386f..2186169 100644
--- a/com/android/settingslib/Utils.java
+++ b/com/android/settingslib/Utils.java
@@ -94,7 +94,7 @@
     public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
         final int iconSize = UserIconDrawable.getSizeForList(context);
         if (user.isManagedProfile()) {
-            Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_icon);
+            Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_badge);
             drawable.setBounds(0, 0, iconSize, iconSize);
             return drawable;
         }
diff --git a/com/android/settingslib/bluetooth/PanProfile.java b/com/android/settingslib/bluetooth/PanProfile.java
index 7bda231..3299cb2 100644
--- a/com/android/settingslib/bluetooth/PanProfile.java
+++ b/com/android/settingslib/bluetooth/PanProfile.java
@@ -32,7 +32,7 @@
 /**
  * PanProfile handles Bluetooth PAN profile (NAP and PANU).
  */
-public final class PanProfile implements LocalBluetoothProfile {
+public class PanProfile implements LocalBluetoothProfile {
     private static final String TAG = "PanProfile";
     private static boolean V = true;
 
diff --git a/com/android/settingslib/bluetooth/Utils.java b/com/android/settingslib/bluetooth/Utils.java
index c919426..0ee1dad 100644
--- a/com/android/settingslib/bluetooth/Utils.java
+++ b/com/android/settingslib/bluetooth/Utils.java
@@ -1,9 +1,17 @@
 package com.android.settingslib.bluetooth;
 
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.util.Pair;
 
 import com.android.settingslib.R;
+import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
+
+import java.util.List;
 
 public class Utils {
     public static final boolean V = false; // verbose logging
@@ -40,4 +48,78 @@
         void onShowError(Context context, String name, int messageResId);
     }
 
+    public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
+            CachedBluetoothDevice cachedDevice) {
+        return getBtClassDrawableWithDescription(context, cachedDevice, 1 /* iconScale */);
+    }
+
+    public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
+            CachedBluetoothDevice cachedDevice, float iconScale) {
+        BluetoothClass btClass = cachedDevice.getBtClass();
+        final int level = cachedDevice.getBatteryLevel();
+        if (btClass != null) {
+            switch (btClass.getMajorDeviceClass()) {
+                case BluetoothClass.Device.Major.COMPUTER:
+                    return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_bt_laptop, level,
+                            iconScale),
+                            context.getString(R.string.bluetooth_talkback_computer));
+
+                case BluetoothClass.Device.Major.PHONE:
+                    return new Pair<>(
+                            getBluetoothDrawable(context, R.drawable.ic_bt_cellphone, level,
+                                    iconScale),
+                            context.getString(R.string.bluetooth_talkback_phone));
+
+                case BluetoothClass.Device.Major.PERIPHERAL:
+                    return new Pair<>(
+                            getBluetoothDrawable(context, HidProfile.getHidClassDrawable(btClass),
+                                    level, iconScale),
+                            context.getString(R.string.bluetooth_talkback_input_peripheral));
+
+                case BluetoothClass.Device.Major.IMAGING:
+                    return new Pair<>(
+                            getBluetoothDrawable(context, R.drawable.ic_settings_print, level,
+                                    iconScale),
+                            context.getString(R.string.bluetooth_talkback_imaging));
+
+                default:
+                    // unrecognized device class; continue
+            }
+        }
+
+        List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+        for (LocalBluetoothProfile profile : profiles) {
+            int resId = profile.getDrawableResource(btClass);
+            if (resId != 0) {
+                return new Pair<>(getBluetoothDrawable(context, resId, level, iconScale), null);
+            }
+        }
+        if (btClass != null) {
+            if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+                return new Pair<>(
+                        getBluetoothDrawable(context, R.drawable.ic_bt_headset_hfp, level,
+                                iconScale),
+                        context.getString(R.string.bluetooth_talkback_headset));
+            }
+            if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
+                return new Pair<>(
+                        getBluetoothDrawable(context, R.drawable.ic_bt_headphones_a2dp, level,
+                                iconScale),
+                        context.getString(R.string.bluetooth_talkback_headphone));
+            }
+        }
+        return new Pair<>(
+                getBluetoothDrawable(context, R.drawable.ic_settings_bluetooth, level, iconScale),
+                context.getString(R.string.bluetooth_talkback_bluetooth));
+    }
+
+    public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId,
+            int batteryLevel, float iconScale) {
+        if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            return BluetoothDeviceLayerDrawable.createLayerDrawable(context, resId, batteryLevel,
+                    iconScale);
+        } else {
+            return context.getDrawable(resId);
+        }
+    }
 }
diff --git a/com/android/settingslib/core/AbstractPreferenceController.java b/com/android/settingslib/core/AbstractPreferenceController.java
index 38fe879..88f0d2b 100644
--- a/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/com/android/settingslib/core/AbstractPreferenceController.java
@@ -10,79 +10,63 @@
  */
 public abstract class AbstractPreferenceController {
 
-  protected final Context mContext;
+    protected final Context mContext;
 
-  public AbstractPreferenceController(Context context) {
-    mContext = context;
-  }
+    public AbstractPreferenceController(Context context) {
+        mContext = context;
+    }
 
-  /**
-   * Displays preference in this controller.
-   */
-  public void displayPreference(PreferenceScreen screen) {
-      if (isAvailable()) {
-          if (this instanceof Preference.OnPreferenceChangeListener) {
-              final Preference preference = screen.findPreference(getPreferenceKey());
-              preference.setOnPreferenceChangeListener(
-                      (Preference.OnPreferenceChangeListener) this);
-          }
-      } else {
-          removePreference(screen, getPreferenceKey());
-      }
-  }
+    /**
+     * Displays preference in this controller.
+     */
+    public void displayPreference(PreferenceScreen screen) {
+        final String prefKey = getPreferenceKey();
+        if (isAvailable()) {
+            setVisible(screen, prefKey, true /* visible */);
+            if (this instanceof Preference.OnPreferenceChangeListener) {
+                final Preference preference = screen.findPreference(prefKey);
+                preference.setOnPreferenceChangeListener(
+                        (Preference.OnPreferenceChangeListener) this);
+            }
+        } else {
+            setVisible(screen, prefKey, false /* visible */);
+        }
+    }
 
-  /**
-   * Updates the current status of preference (summary, switch state, etc)
-   */
-  public void updateState(Preference preference) {
+    /**
+     * Updates the current status of preference (summary, switch state, etc)
+     */
+    public void updateState(Preference preference) {
 
-  }
+    }
 
-  /**
-   * Returns true if preference is available (should be displayed)
-   */
-  public abstract boolean isAvailable();
+    /**
+     * Returns true if preference is available (should be displayed)
+     */
+    public abstract boolean isAvailable();
 
-  /**
-   * Handles preference tree click
-   *
-   * @param preference the preference being clicked
-   * @return true if click is handled
-   */
-  public boolean handlePreferenceTreeClick(Preference preference) {
-      return false;
-  }
+    /**
+     * Handles preference tree click
+     *
+     * @param preference the preference being clicked
+     * @return true if click is handled
+     */
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        return false;
+    }
 
-  /**
-   * Returns the key for this preference.
-   */
-  public abstract String getPreferenceKey();
+    /**
+     * Returns the key for this preference.
+     */
+    public abstract String getPreferenceKey();
 
-  /**
-   * Removes preference from screen.
-   */
-  protected final void removePreference(PreferenceScreen screen, String key) {
-      findAndRemovePreference(screen, key);
-  }
-
-  // finds the preference recursively and removes it from its parent
-  private boolean findAndRemovePreference(PreferenceGroup prefGroup, String key) {
-      final int preferenceCount = prefGroup.getPreferenceCount();
-      for (int i = 0; i < preferenceCount; i++) {
-          final Preference preference = prefGroup.getPreference(i);
-          final String curKey = preference.getKey();
-
-          if (curKey != null && curKey.equals(key)) {
-              return prefGroup.removePreference(preference);
-          }
-
-          if (preference instanceof PreferenceGroup) {
-              if (findAndRemovePreference((PreferenceGroup) preference, key)) {
-                  return true;
-              }
-          }
-      }
-      return false;
-  }
-
+    /**
+     * Show/hide a preference.
+     */
+    protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
+        final Preference pref = group.findPreference(key);
+        if (pref != null) {
+            pref.setVisible(isVisible);
+        }
+    }
 }
diff --git a/com/android/settingslib/core/lifecycle/Lifecycle.java b/com/android/settingslib/core/lifecycle/Lifecycle.java
index b2351a9..451e561 100644
--- a/com/android/settingslib/core/lifecycle/Lifecycle.java
+++ b/com/android/settingslib/core/lifecycle/Lifecycle.java
@@ -15,11 +15,18 @@
  */
 package com.android.settingslib.core.lifecycle;
 
+import static android.arch.lifecycle.Lifecycle.Event.ON_ANY;
+
 import android.annotation.UiThread;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.LifecycleRegistry;
+import android.arch.lifecycle.OnLifecycleEvent;
 import android.content.Context;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.preference.PreferenceScreen;
+import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -44,18 +51,46 @@
 /**
  * Dispatcher for lifecycle events.
  */
-public class Lifecycle {
+public class Lifecycle extends LifecycleRegistry {
+    private static final String TAG = "LifecycleObserver";
 
-    protected final List<LifecycleObserver> mObservers = new ArrayList<>();
+    private final List<LifecycleObserver> mObservers = new ArrayList<>();
+    private final LifecycleProxy mProxy = new LifecycleProxy();
+
+    /**
+     * Creates a new LifecycleRegistry for the given provider.
+     * <p>
+     * You should usually create this inside your LifecycleOwner class's constructor and hold
+     * onto the same instance.
+     *
+     * @param provider The owner LifecycleOwner
+     */
+    public Lifecycle(@NonNull LifecycleOwner provider) {
+        super(provider);
+        addObserver(mProxy);
+    }
 
     /**
      * Registers a new observer of lifecycle events.
      */
     @UiThread
-    public <T extends LifecycleObserver> T addObserver(T observer) {
+    @Override
+    public void addObserver(android.arch.lifecycle.LifecycleObserver observer) {
         ThreadUtils.ensureMainThread();
-        mObservers.add(observer);
-        return observer;
+        super.addObserver(observer);
+        if (observer instanceof LifecycleObserver) {
+            mObservers.add((LifecycleObserver) observer);
+        }
+    }
+
+    @UiThread
+    @Override
+    public void removeObserver(android.arch.lifecycle.LifecycleObserver observer) {
+        ThreadUtils.ensureMainThread();
+        super.removeObserver(observer);
+        if (observer instanceof LifecycleObserver) {
+            mObservers.remove(observer);
+        }
     }
 
     public void onAttach(Context context) {
@@ -67,6 +102,8 @@
         }
     }
 
+    // This method is not called from the proxy because it does not have access to the
+    // savedInstanceState
     public void onCreate(Bundle savedInstanceState) {
         for (int i = 0, size = mObservers.size(); i < size; i++) {
             final LifecycleObserver observer = mObservers.get(i);
@@ -76,7 +113,7 @@
         }
     }
 
-    public void onStart() {
+    private void onStart() {
         for (int i = 0, size = mObservers.size(); i < size; i++) {
             final LifecycleObserver observer = mObservers.get(i);
             if (observer instanceof OnStart) {
@@ -94,7 +131,7 @@
         }
     }
 
-    public void onResume() {
+    private void onResume() {
         for (int i = 0, size = mObservers.size(); i < size; i++) {
             final LifecycleObserver observer = mObservers.get(i);
             if (observer instanceof OnResume) {
@@ -103,7 +140,7 @@
         }
     }
 
-    public void onPause() {
+    private void onPause() {
         for (int i = 0, size = mObservers.size(); i < size; i++) {
             final LifecycleObserver observer = mObservers.get(i);
             if (observer instanceof OnPause) {
@@ -121,7 +158,7 @@
         }
     }
 
-    public void onStop() {
+    private void onStop() {
         for (int i = 0, size = mObservers.size(); i < size; i++) {
             final LifecycleObserver observer = mObservers.get(i);
             if (observer instanceof OnStop) {
@@ -130,7 +167,7 @@
         }
     }
 
-    public void onDestroy() {
+    private void onDestroy() {
         for (int i = 0, size = mObservers.size(); i < size; i++) {
             final LifecycleObserver observer = mObservers.get(i);
             if (observer instanceof OnDestroy) {
@@ -168,4 +205,34 @@
         }
         return false;
     }
+
+    private class LifecycleProxy
+            implements android.arch.lifecycle.LifecycleObserver {
+        @OnLifecycleEvent(ON_ANY)
+        public void onLifecycleEvent(LifecycleOwner owner, Event event) {
+            switch (event) {
+                case ON_CREATE:
+                    // onCreate is called directly since we don't have savedInstanceState here
+                    break;
+                case ON_START:
+                    onStart();
+                    break;
+                case ON_RESUME:
+                    onResume();
+                    break;
+                case ON_PAUSE:
+                    onPause();
+                    break;
+                case ON_STOP:
+                    onStop();
+                    break;
+                case ON_DESTROY:
+                    onDestroy();
+                    break;
+                case ON_ANY:
+                    Log.wtf(TAG, "Should not receive an 'ANY' event!");
+                    break;
+            }
+        }
+    }
 }
diff --git a/com/android/settingslib/core/lifecycle/LifecycleObserver.java b/com/android/settingslib/core/lifecycle/LifecycleObserver.java
index 6c41072..ec8a8b5 100644
--- a/com/android/settingslib/core/lifecycle/LifecycleObserver.java
+++ b/com/android/settingslib/core/lifecycle/LifecycleObserver.java
@@ -17,6 +17,9 @@
 
 /**
  * Observer of lifecycle events.
+ * @deprecated use {@link android.arch.lifecycle.LifecycleObserver} instead
  */
-public interface LifecycleObserver {
+@Deprecated
+public interface LifecycleObserver extends
+        android.arch.lifecycle.LifecycleObserver {
 }
diff --git a/com/android/settingslib/core/lifecycle/ObservableActivity.java b/com/android/settingslib/core/lifecycle/ObservableActivity.java
index 727bec7..8b062f8 100644
--- a/com/android/settingslib/core/lifecycle/ObservableActivity.java
+++ b/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -15,8 +15,16 @@
  */
 package com.android.settingslib.core.lifecycle;
 
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.arch.lifecycle.LifecycleOwner;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.view.Menu;
@@ -25,17 +33,19 @@
 /**
  * {@link Activity} that has hooks to observe activity lifecycle events.
  */
-public class ObservableActivity extends Activity {
+public class ObservableActivity extends Activity implements LifecycleOwner {
 
-    private final Lifecycle mLifecycle = new Lifecycle();
+    private final Lifecycle mLifecycle = new Lifecycle(this);
 
-    protected Lifecycle getLifecycle() {
+    public Lifecycle getLifecycle() {
         return mLifecycle;
     }
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         mLifecycle.onAttach(this);
+        mLifecycle.onCreate(savedInstanceState);
+        mLifecycle.handleLifecycleEvent(ON_CREATE);
         super.onCreate(savedInstanceState);
     }
 
@@ -43,36 +53,38 @@
     public void onCreate(@Nullable Bundle savedInstanceState,
             @Nullable PersistableBundle persistentState) {
         mLifecycle.onAttach(this);
+        mLifecycle.onCreate(savedInstanceState);
+        mLifecycle.handleLifecycleEvent(ON_CREATE);
         super.onCreate(savedInstanceState, persistentState);
     }
 
     @Override
     protected void onStart() {
-        mLifecycle.onStart();
+        mLifecycle.handleLifecycleEvent(ON_START);
         super.onStart();
     }
 
     @Override
     protected void onResume() {
-        mLifecycle.onResume();
+        mLifecycle.handleLifecycleEvent(ON_RESUME);
         super.onResume();
     }
 
     @Override
     protected void onPause() {
-        mLifecycle.onPause();
+        mLifecycle.handleLifecycleEvent(ON_PAUSE);
         super.onPause();
     }
 
     @Override
     protected void onStop() {
-        mLifecycle.onStop();
+        mLifecycle.handleLifecycleEvent(ON_STOP);
         super.onStop();
     }
 
     @Override
     protected void onDestroy() {
-        mLifecycle.onDestroy();
+        mLifecycle.handleLifecycleEvent(ON_DESTROY);
         super.onDestroy();
     }
 
diff --git a/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java b/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
index 315bedc..dc95384 100644
--- a/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
+++ b/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
@@ -15,9 +15,17 @@
  */
 package com.android.settingslib.core.lifecycle;
 
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
 import android.app.DialogFragment;
+import android.arch.lifecycle.LifecycleOwner;
 import android.content.Context;
-import android.support.annotation.VisibleForTesting;
+import android.os.Bundle;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -25,9 +33,9 @@
 /**
  * {@link DialogFragment} that has hooks to observe fragment lifecycle events.
  */
-public class ObservableDialogFragment extends DialogFragment {
+public class ObservableDialogFragment extends DialogFragment implements LifecycleOwner {
 
-    protected final Lifecycle mLifecycle = createLifecycle();
+    protected final Lifecycle mLifecycle = new Lifecycle(this);
 
     @Override
     public void onAttach(Context context) {
@@ -36,32 +44,39 @@
     }
 
     @Override
+    public void onCreate(Bundle savedInstanceState) {
+        mLifecycle.onCreate(savedInstanceState);
+        mLifecycle.handleLifecycleEvent(ON_CREATE);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
     public void onStart() {
-        mLifecycle.onStart();
+        mLifecycle.handleLifecycleEvent(ON_START);
         super.onStart();
     }
 
     @Override
     public void onResume() {
-        mLifecycle.onResume();
+        mLifecycle.handleLifecycleEvent(ON_RESUME);
         super.onResume();
     }
 
     @Override
     public void onPause() {
-        mLifecycle.onPause();
+        mLifecycle.handleLifecycleEvent(ON_PAUSE);
         super.onPause();
     }
 
     @Override
     public void onStop() {
-        mLifecycle.onStop();
+        mLifecycle.handleLifecycleEvent(ON_STOP);
         super.onStop();
     }
 
     @Override
     public void onDestroy() {
-        mLifecycle.onDestroy();
+        mLifecycle.handleLifecycleEvent(ON_DESTROY);
         super.onDestroy();
     }
 
@@ -86,9 +101,8 @@
         return lifecycleHandled;
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
-    /** @return a new lifecycle. */
-    public static Lifecycle createLifecycle() {
-        return new Lifecycle();
+    @Override
+    public Lifecycle getLifecycle() {
+        return mLifecycle;
     }
 }
diff --git a/com/android/settingslib/core/lifecycle/ObservableFragment.java b/com/android/settingslib/core/lifecycle/ObservableFragment.java
index 3a00eba..925eda6 100644
--- a/com/android/settingslib/core/lifecycle/ObservableFragment.java
+++ b/com/android/settingslib/core/lifecycle/ObservableFragment.java
@@ -16,19 +16,27 @@
 
 package com.android.settingslib.core.lifecycle;
 
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
 import android.annotation.CallSuper;
 import android.app.Fragment;
+import android.arch.lifecycle.LifecycleOwner;
 import android.content.Context;
 import android.os.Bundle;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 
-public class ObservableFragment extends Fragment {
+public class ObservableFragment extends Fragment implements LifecycleOwner {
 
-    private final Lifecycle mLifecycle = new Lifecycle();
+    private final Lifecycle mLifecycle = new Lifecycle(this);
 
-    protected Lifecycle getLifecycle() {
+    public Lifecycle getLifecycle() {
         return mLifecycle;
     }
 
@@ -43,6 +51,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         mLifecycle.onCreate(savedInstanceState);
+        mLifecycle.handleLifecycleEvent(ON_CREATE);
         super.onCreate(savedInstanceState);
     }
 
@@ -56,35 +65,35 @@
     @CallSuper
     @Override
     public void onStart() {
-        mLifecycle.onStart();
+        mLifecycle.handleLifecycleEvent(ON_START);
         super.onStart();
     }
 
     @CallSuper
     @Override
-    public void onStop() {
-        mLifecycle.onStop();
-        super.onStop();
-    }
-
-    @CallSuper
-    @Override
     public void onResume() {
-        mLifecycle.onResume();
+        mLifecycle.handleLifecycleEvent(ON_RESUME);
         super.onResume();
     }
 
     @CallSuper
     @Override
     public void onPause() {
-        mLifecycle.onPause();
+        mLifecycle.handleLifecycleEvent(ON_PAUSE);
         super.onPause();
     }
 
     @CallSuper
     @Override
+    public void onStop() {
+        mLifecycle.handleLifecycleEvent(ON_STOP);
+        super.onStop();
+    }
+
+    @CallSuper
+    @Override
     public void onDestroy() {
-        mLifecycle.onDestroy();
+        mLifecycle.handleLifecycleEvent(ON_DESTROY);
         super.onDestroy();
     }
 
diff --git a/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
index 76e5c85..abd7755 100644
--- a/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
+++ b/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
@@ -16,7 +16,15 @@
 package com.android.settingslib.core.lifecycle;
 
 
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
 import android.annotation.CallSuper;
+import android.arch.lifecycle.LifecycleOwner;
 import android.content.Context;
 import android.os.Bundle;
 import android.support.v14.preference.PreferenceFragment;
@@ -28,11 +36,12 @@
 /**
  * {@link PreferenceFragment} that has hooks to observe fragment lifecycle events.
  */
-public abstract class ObservablePreferenceFragment extends PreferenceFragment {
+public abstract class ObservablePreferenceFragment extends PreferenceFragment
+        implements LifecycleOwner {
 
-    private final Lifecycle mLifecycle = new Lifecycle();
+    private final Lifecycle mLifecycle = new Lifecycle(this);
 
-    protected Lifecycle getLifecycle() {
+    public Lifecycle getLifecycle() {
         return mLifecycle;
     }
 
@@ -47,6 +56,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         mLifecycle.onCreate(savedInstanceState);
+        mLifecycle.handleLifecycleEvent(ON_CREATE);
         super.onCreate(savedInstanceState);
     }
 
@@ -66,35 +76,35 @@
     @CallSuper
     @Override
     public void onStart() {
-        mLifecycle.onStart();
+        mLifecycle.handleLifecycleEvent(ON_START);
         super.onStart();
     }
 
     @CallSuper
     @Override
-    public void onStop() {
-        mLifecycle.onStop();
-        super.onStop();
-    }
-
-    @CallSuper
-    @Override
     public void onResume() {
-        mLifecycle.onResume();
+        mLifecycle.handleLifecycleEvent(ON_RESUME);
         super.onResume();
     }
 
     @CallSuper
     @Override
     public void onPause() {
-        mLifecycle.onPause();
+        mLifecycle.handleLifecycleEvent(ON_PAUSE);
         super.onPause();
     }
 
     @CallSuper
     @Override
+    public void onStop() {
+        mLifecycle.handleLifecycleEvent(ON_STOP);
+        super.onStop();
+    }
+
+    @CallSuper
+    @Override
     public void onDestroy() {
-        mLifecycle.onDestroy();
+        mLifecycle.handleLifecycleEvent(ON_DESTROY);
         super.onDestroy();
     }
 
diff --git a/com/android/settingslib/core/lifecycle/events/OnAttach.java b/com/android/settingslib/core/lifecycle/events/OnAttach.java
index 152cbac..e28c387 100644
--- a/com/android/settingslib/core/lifecycle/events/OnAttach.java
+++ b/com/android/settingslib/core/lifecycle/events/OnAttach.java
@@ -17,6 +17,10 @@
 
 import android.content.Context;
 
+/**
+ * @deprecated pass {@link Context} in constructor instead
+ */
+@Deprecated
 public interface OnAttach {
     void onAttach(Context context);
 }
diff --git a/com/android/settingslib/core/lifecycle/events/OnCreate.java b/com/android/settingslib/core/lifecycle/events/OnCreate.java
index 44cbf8d..ad7068e 100644
--- a/com/android/settingslib/core/lifecycle/events/OnCreate.java
+++ b/com/android/settingslib/core/lifecycle/events/OnCreate.java
@@ -16,8 +16,14 @@
 package com.android.settingslib.core.lifecycle.events;
 
 
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
 import android.os.Bundle;
 
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
 public interface OnCreate {
     void onCreate(Bundle savedInstanceState);
 }
diff --git a/com/android/settingslib/core/lifecycle/events/OnDestroy.java b/com/android/settingslib/core/lifecycle/events/OnDestroy.java
index ffa3d16..c37286e 100644
--- a/com/android/settingslib/core/lifecycle/events/OnDestroy.java
+++ b/com/android/settingslib/core/lifecycle/events/OnDestroy.java
@@ -15,6 +15,13 @@
  */
 package com.android.settingslib.core.lifecycle.events;
 
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
 public interface OnDestroy {
     void onDestroy();
 }
diff --git a/com/android/settingslib/core/lifecycle/events/OnPause.java b/com/android/settingslib/core/lifecycle/events/OnPause.java
index 4a71105..a5ab39c 100644
--- a/com/android/settingslib/core/lifecycle/events/OnPause.java
+++ b/com/android/settingslib/core/lifecycle/events/OnPause.java
@@ -15,6 +15,13 @@
  */
 package com.android.settingslib.core.lifecycle.events;
 
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
 public interface OnPause {
     void onPause();
 }
diff --git a/com/android/settingslib/core/lifecycle/events/OnResume.java b/com/android/settingslib/core/lifecycle/events/OnResume.java
index 8dd24e9..1effba4 100644
--- a/com/android/settingslib/core/lifecycle/events/OnResume.java
+++ b/com/android/settingslib/core/lifecycle/events/OnResume.java
@@ -15,6 +15,13 @@
  */
 package com.android.settingslib.core.lifecycle.events;
 
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event)}
+ */
+@Deprecated
 public interface OnResume {
     void onResume();
 }
diff --git a/com/android/settingslib/core/lifecycle/events/OnStart.java b/com/android/settingslib/core/lifecycle/events/OnStart.java
index c88ddaa..07b8460 100644
--- a/com/android/settingslib/core/lifecycle/events/OnStart.java
+++ b/com/android/settingslib/core/lifecycle/events/OnStart.java
@@ -15,7 +15,13 @@
  */
 package com.android.settingslib.core.lifecycle.events;
 
-public interface OnStart {
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
 
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
+public interface OnStart {
     void onStart();
 }
diff --git a/com/android/settingslib/core/lifecycle/events/OnStop.java b/com/android/settingslib/core/lifecycle/events/OnStop.java
index 32f61d9..d6a5967 100644
--- a/com/android/settingslib/core/lifecycle/events/OnStop.java
+++ b/com/android/settingslib/core/lifecycle/events/OnStop.java
@@ -15,7 +15,13 @@
  */
 package com.android.settingslib.core.lifecycle.events;
 
-public interface OnStop {
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.OnLifecycleEvent;
 
+/**
+ * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) }
+ */
+@Deprecated
+public interface OnStop {
     void onStop();
 }
diff --git a/com/android/settingslib/datetime/ZoneGetter.java b/com/android/settingslib/datetime/ZoneGetter.java
index 1771208..a8262c8 100644
--- a/com/android/settingslib/datetime/ZoneGetter.java
+++ b/com/android/settingslib/datetime/ZoneGetter.java
@@ -20,6 +20,7 @@
 import android.content.res.XmlResourceParser;
 import android.icu.text.TimeZoneFormat;
 import android.icu.text.TimeZoneNames;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.text.BidiFormatter;
 import android.support.v4.text.TextDirectionHeuristicsCompat;
 import android.text.SpannableString;
@@ -32,6 +33,8 @@
 
 import com.android.settingslib.R;
 
+import libcore.util.TimeZoneFinder;
+
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.util.ArrayList;
@@ -349,7 +352,8 @@
         return gmtText;
     }
 
-    private static final class ZoneGetterData {
+    @VisibleForTesting
+    public static final class ZoneGetterData {
         public final String[] olsonIdsToDisplay;
         public final CharSequence[] gmtOffsetTexts;
         public final TimeZone[] timeZones;
@@ -376,10 +380,13 @@
             }
 
             // Create a lookup of local zone IDs.
-            localZoneIds = new HashSet<String>();
-            for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) {
-                localZoneIds.add(olsonId);
-            }
+            final List<String> zoneIds = lookupTimeZoneIdsByCountry(locale.getCountry());
+            localZoneIds = new HashSet<>(zoneIds);
+        }
+
+        @VisibleForTesting
+        public List<String> lookupTimeZoneIdsByCountry(String country) {
+            return TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(country);
         }
     }
 }
diff --git a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 6aae226..3c02f6a 100644
--- a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -16,11 +16,13 @@
 
 package com.android.settingslib.development;
 
+import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
 import android.support.v14.preference.SwitchPreference;
 import android.support.v4.content.LocalBroadcastManager;
 import android.support.v7.preference.Preference;
@@ -95,6 +97,10 @@
 
     @Override
     public boolean handlePreferenceTreeClick(Preference preference) {
+        if (isUserAMonkey()) {
+            return false;
+        }
+
         if (TextUtils.equals(KEY_ENABLE_ADB, preference.getKey())) {
             if (!isAdbEnabled()) {
                 showConfirmationDialog(preference);
@@ -117,4 +123,9 @@
         LocalBroadcastManager.getInstance(mContext)
                 .sendBroadcast(new Intent(ACTION_ENABLE_ADB_STATE_CHANGED));
     }
+
+    @VisibleForTesting
+    boolean isUserAMonkey() {
+        return ActivityManager.isUserAMonkey();
+    }
 }
diff --git a/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
index ff7536a..90f14ef 100644
--- a/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
+++ b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
@@ -18,18 +18,20 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.support.annotation.VisibleForTesting;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
 import android.text.TextUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.core.AbstractPreferenceController;
 
 /**
  * Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}.
  */
 public class AbstractSerialNumberPreferenceController extends AbstractPreferenceController {
-    private static final String KEY_SERIAL_NUMBER = "serial_number";
+
+    @VisibleForTesting
+    static final String KEY_SERIAL_NUMBER = "serial_number";
 
     private final String mSerialNumber;
 
diff --git a/com/android/settingslib/drawer/UserAdapter.java b/com/android/settingslib/drawer/UserAdapter.java
index 750601d..b27d823 100644
--- a/com/android/settingslib/drawer/UserAdapter.java
+++ b/com/android/settingslib/drawer/UserAdapter.java
@@ -57,7 +57,7 @@
             if (userInfo.isManagedProfile()) {
                 mName = context.getString(R.string.managed_user_title);
                 icon = context.getDrawable(
-                    com.android.internal.R.drawable.ic_corp_icon);
+                    com.android.internal.R.drawable.ic_corp_badge);
             } else {
                 mName = userInfo.name;
                 final int userId = userInfo.id;
diff --git a/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index f4c9bb3..4fe9d56 100644
--- a/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.graph;
 
-import android.animation.ArgbEvaluator;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
@@ -100,7 +99,7 @@
 
         final int N = levels.length();
         mColors = new int[2 * N];
-        for (int i=0; i < N; i++) {
+        for (int i = 0; i < N; i++) {
             mColors[2 * i] = levels.getInt(i, 0);
             if (colors.getType(i) == TypedValue.TYPE_ATTRIBUTE) {
                 mColors[2 * i + 1] = Utils.getColorAttr(context, colors.getThemeAttributeId(i, 0));
diff --git a/com/android/settingslib/wifi/AccessPointPreference.java b/com/android/settingslib/wifi/AccessPointPreference.java
index fdbbf14..dd55188 100644
--- a/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/com/android/settingslib/wifi/AccessPointPreference.java
@@ -31,15 +31,17 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.SparseArray;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.settingslib.R;
 import com.android.settingslib.TronUtils;
+import com.android.settingslib.TwoTargetPreference;
 import com.android.settingslib.Utils;
 import com.android.settingslib.wifi.AccessPoint.Speed;
 
-public class AccessPointPreference extends Preference {
+public class AccessPointPreference extends TwoTargetPreference {
 
     private static final int[] STATE_SECURED = {
             R.attr.state_encrypted
@@ -126,7 +128,6 @@
                           int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld,
                           int level, IconInjector iconInjector) {
         super(context);
-        setWidgetLayoutResource(R.layout.access_point_friction_widget);
         mBadgeCache = cache;
         mAccessPoint = accessPoint;
         mForSavedNetworks = forSavedNetworks;
@@ -165,6 +166,20 @@
 
         ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
         bindFrictionImage(frictionImageView);
+        setDividerVisibility(view, View.GONE);
+    }
+
+    protected void setDividerVisibility(final PreferenceViewHolder view,
+            @View.Visibility int visibility) {
+        final View divider = view.findViewById(R.id.two_target_divider);
+        if (divider != null) {
+            divider.setVisibility(visibility);
+        }
+    }
+
+    @Override
+    protected int getSecondTargetResId() {
+        return R.layout.access_point_friction_widget;
     }
 
     protected void updateIcon(int level, Context context) {
diff --git a/com/android/setupwizardlib/GlifLayout.java b/com/android/setupwizardlib/GlifLayout.java
index f4d52a5..dd0963b 100644
--- a/com/android/setupwizardlib/GlifLayout.java
+++ b/com/android/setupwizardlib/GlifLayout.java
@@ -77,6 +77,8 @@
     @Nullable
     private ColorStateList mBackgroundBaseColor;
 
+    private boolean mLayoutFullscreen = true;
+
     public GlifLayout(Context context) {
         this(context, 0, 0);
     }
@@ -139,7 +141,13 @@
             inflateFooter(footer);
         }
 
+        mLayoutFullscreen = a.getBoolean(R.styleable.SuwGlifLayout_suwLayoutFullscreen, true);
+
         a.recycle();
+
+        if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && mLayoutFullscreen) {
+            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+        }
     }
 
     @Override
@@ -280,9 +288,6 @@
                 patternBg.setBackgroundDrawable(background);
             }
         }
-        if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
-            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-        }
     }
 
     public boolean isProgressBarShown() {
diff --git a/com/android/setupwizardlib/GlifLayoutTest.java b/com/android/setupwizardlib/GlifLayoutTest.java
index d46409d..967a52e 100644
--- a/com/android/setupwizardlib/GlifLayoutTest.java
+++ b/com/android/setupwizardlib/GlifLayoutTest.java
@@ -32,6 +32,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.os.Build.VERSION_CODES;
 import android.support.annotation.IdRes;
 import android.view.ContextThemeWrapper;
 import android.view.View;
@@ -266,6 +267,32 @@
         assertNotNull(layout.findViewById(android.R.id.text1));
     }
 
+    @Config(sdk = { VERSION_CODES.M, Config.NEWEST_SDK })
+    @Test
+    public void createFromXml_shouldSetLayoutFullscreen_whenLayoutFullscreenIsNotSet() {
+        GlifLayout layout = new GlifLayout(
+                mContext,
+                Robolectric.buildAttributeSet()
+                        .build());
+
+        assertEquals(
+                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+                layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+    }
+
+    @Test
+    public void createFromXml_shouldNotSetLayoutFullscreen_whenLayoutFullscreenIsFalse() {
+        GlifLayout layout = new GlifLayout(
+                mContext,
+                Robolectric.buildAttributeSet()
+                        .addAttribute(R.attr.suwLayoutFullscreen, "false")
+                        .build());
+
+        assertEquals(
+                0,
+                layout.getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+    }
+
     private Drawable getPhoneBackground(GlifLayout layout) {
         final StatusBarBackgroundLayout patternBg =
                 (StatusBarBackgroundLayout) layout.findManagedViewById(R.id.suw_pattern_bg);
diff --git a/com/android/setupwizardlib/template/IconMixin.java b/com/android/setupwizardlib/template/IconMixin.java
index 46c23f0..c42299b 100644
--- a/com/android/setupwizardlib/template/IconMixin.java
+++ b/com/android/setupwizardlib/template/IconMixin.java
@@ -20,6 +20,7 @@
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.ImageView;
 
 import com.android.setupwizardlib.R;
@@ -61,6 +62,7 @@
         final ImageView iconView = getView();
         if (iconView != null) {
             iconView.setImageDrawable(icon);
+            iconView.setVisibility(icon != null ? View.VISIBLE : View.GONE);
         }
     }
 
diff --git a/com/android/setupwizardlib/template/IconMixinTest.java b/com/android/setupwizardlib/template/IconMixinTest.java
index a1f2b54..0391392 100644
--- a/com/android/setupwizardlib/template/IconMixinTest.java
+++ b/com/android/setupwizardlib/template/IconMixinTest.java
@@ -31,6 +31,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Xml;
+import android.view.View;
 import android.widget.ImageView;
 
 import com.android.setupwizardlib.TemplateLayout;
@@ -74,6 +75,15 @@
         mixin.setIcon(drawable);
 
         assertSame(drawable, mIconView.getDrawable());
+        assertEquals(View.VISIBLE, mIconView.getVisibility());
+    }
+
+    @Test
+    public void setIcon_shouldSetVisibilityToGone_whenIconIsNull() {
+        IconMixin mixin = new IconMixin(mTemplateLayout, null, 0);
+        mixin.setIcon(null);
+
+        assertEquals(View.GONE, mIconView.getVisibility());
     }
 
     @Test
@@ -101,5 +111,6 @@
                 .getDrawable(android.R.drawable.ic_menu_add);
         final BitmapDrawable actual = (BitmapDrawable) mIconView.getDrawable();
         assertEquals(expected.getBitmap(), actual.getBitmap());
+        assertEquals(View.VISIBLE, mIconView.getVisibility());
     }
 }
diff --git a/com/android/setupwizardlib/util/WizardManagerHelper.java b/com/android/setupwizardlib/util/WizardManagerHelper.java
index 896c013..32929aa 100644
--- a/com/android/setupwizardlib/util/WizardManagerHelper.java
+++ b/com/android/setupwizardlib/util/WizardManagerHelper.java
@@ -27,6 +27,8 @@
 
 import com.android.setupwizardlib.R;
 
+import java.util.Arrays;
+
 public class WizardManagerHelper {
 
     private static final String ACTION_NEXT = "com.android.wizard.NEXT";
@@ -142,13 +144,14 @@
      */
     public static void copyWizardManagerExtras(Intent srcIntent, Intent dstIntent) {
         dstIntent.putExtra(EXTRA_WIZARD_BUNDLE, srcIntent.getBundleExtra(EXTRA_WIZARD_BUNDLE));
-        dstIntent.putExtra(EXTRA_THEME, srcIntent.getStringExtra(EXTRA_THEME));
-        dstIntent.putExtra(EXTRA_IS_FIRST_RUN,
-                srcIntent.getBooleanExtra(EXTRA_IS_FIRST_RUN, false));
-        dstIntent.putExtra(EXTRA_IS_DEFERRED_SETUP,
-                srcIntent.getBooleanExtra(EXTRA_IS_DEFERRED_SETUP, false));
-        dstIntent.putExtra(EXTRA_SCRIPT_URI, srcIntent.getStringExtra(EXTRA_SCRIPT_URI));
-        dstIntent.putExtra(EXTRA_ACTION_ID, srcIntent.getStringExtra(EXTRA_ACTION_ID));
+        for (String key : Arrays.asList(
+                EXTRA_IS_FIRST_RUN, EXTRA_IS_DEFERRED_SETUP, EXTRA_IS_PRE_DEFERRED_SETUP)) {
+            dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false));
+        }
+
+        for (String key : Arrays.asList(EXTRA_THEME, EXTRA_SCRIPT_URI, EXTRA_ACTION_ID)) {
+            dstIntent.putExtra(key, srcIntent.getStringExtra(key));
+        }
     }
 
     /**
diff --git a/com/android/setupwizardlib/util/WizardManagerHelperTest.java b/com/android/setupwizardlib/util/WizardManagerHelperTest.java
index 6477b51..c236bb5 100644
--- a/com/android/setupwizardlib/util/WizardManagerHelperTest.java
+++ b/com/android/setupwizardlib/util/WizardManagerHelperTest.java
@@ -274,6 +274,7 @@
                 .putExtra(WizardManagerHelper.EXTRA_WIZARD_BUNDLE, wizardBundle)
                 .putExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, true)
                 .putExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, true)
+                .putExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, true)
                 // Script URI and Action ID are kept for backwards compatibility
                 .putExtra(WizardManagerHelper.EXTRA_SCRIPT_URI, "test_script_uri")
                 .putExtra(WizardManagerHelper.EXTRA_ACTION_ID, "test_action_id");
@@ -292,6 +293,8 @@
                 intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_FIRST_RUN, false));
         assertTrue("EXTRA_IS_DEFERRED_SETUP should be copied",
                 intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_DEFERRED_SETUP, false));
+        assertTrue("EXTRA_IS_PRE_DEFERRED_SETUP should be copied",
+                intent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_PRE_DEFERRED_SETUP, false));
 
         // Script URI and Action ID are replaced by Wizard Bundle in M, but are kept for backwards
         // compatibility
diff --git a/com/android/systemui/DemoMode.java b/com/android/systemui/DemoMode.java
index 11996d0..5c39715 100644
--- a/com/android/systemui/DemoMode.java
+++ b/com/android/systemui/DemoMode.java
@@ -37,4 +37,5 @@
     public static final String COMMAND_STATUS = "status";
     public static final String COMMAND_NOTIFICATIONS = "notifications";
     public static final String COMMAND_VOLUME = "volume";
+    public static final String COMMAND_OPERATOR = "operator";
 }
diff --git a/com/android/systemui/Dependency.java b/com/android/systemui/Dependency.java
index d8a47c5..e7e70af 100644
--- a/com/android/systemui/Dependency.java
+++ b/com/android/systemui/Dependency.java
@@ -26,7 +26,7 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.Preconditions;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -205,8 +205,8 @@
         mProviders.put(BatteryController.class, () ->
                 new BatteryControllerImpl(mContext));
 
-        mProviders.put(NightDisplayController.class, () ->
-                new NightDisplayController(mContext));
+        mProviders.put(ColorDisplayController.class, () ->
+                new ColorDisplayController(mContext));
 
         mProviders.put(ManagedProfileController.class, () ->
                 new ManagedProfileControllerImpl(mContext));
@@ -308,6 +308,8 @@
 
         mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService());
 
+        mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext));
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/com/android/systemui/OverviewProxyService.java b/com/android/systemui/OverviewProxyService.java
new file mode 100644
index 0000000..2e4a5a4
--- /dev/null
+++ b/com/android/systemui/OverviewProxyService.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PatternMatcher;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.SurfaceControl;
+
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to send information from overview to launcher with a binder.
+ */
+public class OverviewProxyService implements CallbackController<OverviewProxyListener>, Dumpable {
+
+    private static final String TAG = "OverviewProxyService";
+    private static final long BACKOFF_MILLIS = 5000;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
+    private final ComponentName mLauncherComponentName;
+    private final DeviceProvisionedController mDeviceProvisionedController
+            = Dependency.get(DeviceProvisionedController.class);
+    private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
+
+    private IOverviewProxy mOverviewProxy;
+    private int mConnectionBackoffAttempts;
+
+    private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
+        public Bitmap screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+                boolean useIdentityTransform, int rotation) {
+            long token = Binder.clearCallingIdentity();
+            try {
+                return SurfaceControl.screenshot(sourceCrop, width, height, minLayer, maxLayer,
+                        useIdentityTransform, rotation);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    };
+
+    private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Reconnect immediately, instead of waiting for resume to arrive.
+            startConnectionToCurrentUser();
+        }
+    };
+
+    private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (service != null) {
+                mConnectionBackoffAttempts = 0;
+                mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
+                // Listen for launcher's death
+                try {
+                    service.linkToDeath(mOverviewServiceDeathRcpt, 0);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Lost connection to launcher service", e);
+                }
+                try {
+                    mOverviewProxy.onBind(mSysUiProxy);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to call onBind()", e);
+                }
+                notifyConnectionChanged();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            // Do nothing
+        }
+    };
+
+    private final DeviceProvisionedListener mDeviceProvisionedCallback =
+                new DeviceProvisionedListener() {
+            @Override
+            public void onUserSetupChanged() {
+                if (mDeviceProvisionedController.isCurrentUserSetup()) {
+                    internalConnectToCurrentUser();
+                }
+            }
+
+            @Override
+            public void onUserSwitched() {
+                mConnectionBackoffAttempts = 0;
+                internalConnectToCurrentUser();
+            }
+        };
+
+    // This is the death handler for the binder from the launcher service
+    private final IBinder.DeathRecipient mOverviewServiceDeathRcpt
+            = this::startConnectionToCurrentUser;
+
+    public OverviewProxyService(Context context) {
+        mContext = context;
+        mHandler = new Handler();
+        mConnectionBackoffAttempts = 0;
+        mLauncherComponentName = ComponentName
+                .unflattenFromString(context.getString(R.string.config_overviewServiceComponent));
+        mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
+
+        // Listen for the package update changes.
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(mLauncherComponentName.getPackageName(),
+                PatternMatcher.PATTERN_LITERAL);
+        mContext.registerReceiver(mLauncherAddedReceiver, filter);
+    }
+
+    public void startConnectionToCurrentUser() {
+        if (mHandler.getLooper() != Looper.myLooper()) {
+            mHandler.post(mConnectionRunnable);
+        } else {
+            internalConnectToCurrentUser();
+        }
+    }
+
+    private void internalConnectToCurrentUser() {
+        disconnectFromLauncherService();
+
+        // If user has not setup yet or already connected, do not try to connect
+        if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+            return;
+        }
+        mHandler.removeCallbacks(mConnectionRunnable);
+        Intent launcherServiceIntent = new Intent();
+        launcherServiceIntent.setComponent(mLauncherComponentName);
+        boolean bound = mContext.bindServiceAsUser(launcherServiceIntent,
+                mOverviewServiceConnection, Context.BIND_AUTO_CREATE,
+                UserHandle.getUserHandleForUid(mDeviceProvisionedController.getCurrentUser()));
+        if (!bound) {
+            // Retry after exponential backoff timeout
+            final long timeoutMs = (long) Math.scalb(BACKOFF_MILLIS, mConnectionBackoffAttempts);
+            mHandler.postDelayed(mConnectionRunnable, timeoutMs);
+            mConnectionBackoffAttempts++;
+        }
+    }
+
+    @Override
+    public void addCallback(OverviewProxyListener listener) {
+        mConnectionCallbacks.add(listener);
+        listener.onConnectionChanged(mOverviewProxy != null);
+    }
+
+    @Override
+    public void removeCallback(OverviewProxyListener listener) {
+        mConnectionCallbacks.remove(listener);
+    }
+
+    public IOverviewProxy getProxy() {
+        return mOverviewProxy;
+    }
+
+    private void disconnectFromLauncherService() {
+        if (mOverviewProxy != null) {
+            mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0);
+            mContext.unbindService(mOverviewServiceConnection);
+            mOverviewProxy = null;
+            notifyConnectionChanged();
+        }
+    }
+
+    private void notifyConnectionChanged() {
+        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+            mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(TAG + " state:");
+        pw.print("  mConnectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts);
+        pw.print("  isCurrentUserSetup="); pw.println(mDeviceProvisionedController
+                .isCurrentUserSetup());
+        pw.print("  isConnected="); pw.println(mOverviewProxy != null);
+    }
+
+    public interface OverviewProxyListener {
+        void onConnectionChanged(boolean isConnected);
+    }
+}
diff --git a/com/android/systemui/RecentsComponent.java b/com/android/systemui/RecentsComponent.java
index 44a044b..880ae70 100644
--- a/com/android/systemui/RecentsComponent.java
+++ b/com/android/systemui/RecentsComponent.java
@@ -28,7 +28,7 @@
     /**
      * Docks the top-most task and opens recents.
      */
-    boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+    boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
             int metricsDockAction);
 
     /**
diff --git a/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 5d0a9d7..03b0082 100644
--- a/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -33,8 +33,10 @@
 
     @Override
     public void setDozeScreenState(int state) {
-        if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
+        if (state == Display.STATE_DOZE) {
             state = Display.STATE_ON;
+        } else if (state == Display.STATE_DOZE_SUSPEND) {
+            state = Display.STATE_ON_SUSPEND;
         }
         super.setDozeScreenState(state);
     }
diff --git a/com/android/systemui/doze/DozeService.java b/com/android/systemui/doze/DozeService.java
index 98b1106..6650cc6 100644
--- a/com/android/systemui/doze/DozeService.java
+++ b/com/android/systemui/doze/DozeService.java
@@ -92,6 +92,7 @@
 
     @Override
     protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dumpOnHandler(fd, pw, args);
         if (mDozeMachine != null) {
             mDozeMachine.dump(pw);
         }
diff --git a/com/android/systemui/globalactions/GlobalActionsDialog.java b/com/android/systemui/globalactions/GlobalActionsDialog.java
index d82f9cd..00e8b1a 100644
--- a/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -80,7 +80,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
+import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/com/android/systemui/keyguard/KeyguardSliceProvider.java b/com/android/systemui/keyguard/KeyguardSliceProvider.java
new file mode 100644
index 0000000..03018f7
--- /dev/null
+++ b/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
+import android.net.Uri;
+import android.os.Handler;
+import android.app.slice.Slice;
+import android.app.slice.SliceProvider;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Simple Slice provider that shows the current date.
+ */
+public class KeyguardSliceProvider extends SliceProvider {
+
+    public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+
+    private final Date mCurrentTime = new Date();
+    protected final Uri mSliceUri;
+    private final Handler mHandler;
+    private String mDatePattern;
+    private DateFormat mDateFormat;
+    private String mLastText;
+    private boolean mRegistered;
+    private boolean mRegisteredEveryMinute;
+
+    /**
+     * Receiver responsible for time ticking and updating the date format.
+     */
+    @VisibleForTesting
+    final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_TIME_TICK.equals(action)
+                    || Intent.ACTION_DATE_CHANGED.equals(action)
+                    || Intent.ACTION_TIME_CHANGED.equals(action)
+                    || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+                    || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+                if (Intent.ACTION_LOCALE_CHANGED.equals(action)
+                        || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
+                    // need to get a fresh date format
+                    mHandler.post(KeyguardSliceProvider.this::cleanDateFormat);
+                }
+                mHandler.post(KeyguardSliceProvider.this::updateClock);
+            }
+        }
+    };
+
+    public KeyguardSliceProvider() {
+        this(new Handler());
+    }
+
+    @VisibleForTesting
+    KeyguardSliceProvider(Handler handler) {
+        mHandler = handler;
+        mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+    }
+
+    @Override
+    public Slice onBindSlice(Uri sliceUri) {
+        return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+    }
+
+    @Override
+    public boolean onCreate() {
+
+        mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
+
+        registerClockUpdate(false /* everyMinute */);
+        updateClock();
+        return true;
+    }
+
+    protected void registerClockUpdate(boolean everyMinute) {
+        if (mRegistered) {
+            if (mRegisteredEveryMinute == everyMinute) {
+                return;
+            } else {
+                unregisterClockUpdate();
+            }
+        }
+
+        IntentFilter filter = new IntentFilter();
+        if (everyMinute) {
+            filter.addAction(Intent.ACTION_TIME_TICK);
+        }
+        filter.addAction(Intent.ACTION_DATE_CHANGED);
+        filter.addAction(Intent.ACTION_TIME_CHANGED);
+        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+        getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
+                null /* scheduler */);
+        mRegistered = true;
+        mRegisteredEveryMinute = everyMinute;
+    }
+
+    protected void unregisterClockUpdate() {
+        if (!mRegistered) {
+            return;
+        }
+        getContext().unregisterReceiver(mIntentReceiver);
+        mRegistered = false;
+    }
+
+    @VisibleForTesting
+    boolean isRegistered() {
+        return mRegistered;
+    }
+
+    protected void updateClock() {
+        final String text = getFormattedDate();
+        if (!text.equals(mLastText)) {
+            mLastText = text;
+            getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+        }
+    }
+
+    protected String getFormattedDate() {
+        if (mDateFormat == null) {
+            final Locale l = Locale.getDefault();
+            DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
+            format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+            mDateFormat = format;
+        }
+        mCurrentTime.setTime(System.currentTimeMillis());
+        return mDateFormat.format(mCurrentTime);
+    }
+
+    @VisibleForTesting
+    void cleanDateFormat() {
+        mDateFormat = null;
+    }
+}
diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java
index 28adca9..a35ba9f 100644
--- a/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -28,6 +28,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
 import android.app.trust.TrustManager;
@@ -1691,6 +1692,8 @@
             mUiOffloadThread.submit(() -> {
                 // If the stream is muted, don't play the sound
                 if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
+                // If DND blocks the sound, don't play the sound
+                if (areSystemSoundsZenModeBlocked(mContext)) return;
 
                 int id = mLockSounds.play(soundId,
                         mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
@@ -1702,6 +1705,25 @@
         }
     }
 
+    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;
+        }
+    }
+
     private void playTrustedSound() {
         playSound(mTrustedSoundId);
     }
diff --git a/com/android/systemui/keyguard/WorkLockActivityController.java b/com/android/systemui/keyguard/WorkLockActivityController.java
index 4c3d5ba..b9e9e0a 100644
--- a/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -26,34 +26,42 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 public class WorkLockActivityController {
+    private static final String TAG = WorkLockActivityController.class.getSimpleName();
+
     private final Context mContext;
-    private final SystemServicesProxy mSsp;
     private final IActivityManager mIam;
 
     public WorkLockActivityController(Context context) {
-        this(context, SystemServicesProxy.getInstance(context), ActivityManager.getService());
+        this(context, ActivityManagerWrapper.getInstance(), ActivityManager.getService());
     }
 
     @VisibleForTesting
-    WorkLockActivityController(Context context, SystemServicesProxy ssp, IActivityManager am) {
+    WorkLockActivityController(Context context, ActivityManagerWrapper am, IActivityManager iAm) {
         mContext = context;
-        mSsp = ssp;
-        mIam = am;
+        mIam = iAm;
 
-        mSsp.registerTaskStackListener(mLockListener);
+        am.registerTaskStackListener(mLockListener);
     }
 
     private void startWorkChallengeInTask(int taskId, int userId) {
+        ActivityManager.TaskDescription taskDescription = null;
+        try {
+            taskDescription = mIam.getTaskDescription(taskId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to get description for task=" + taskId);
+        }
         Intent intent = new Intent(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER)
                 .setComponent(new ComponentName(mContext, WorkLockActivity.class))
                 .putExtra(Intent.EXTRA_USER_ID, userId)
-                .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, mSsp.getTaskDescription(taskId))
+                .putExtra(WorkLockActivity.EXTRA_TASK_DESCRIPTION, taskDescription)
                 .addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
@@ -67,7 +75,11 @@
         } else {
             // Starting the activity inside the task failed. We can't be sure why, so to be
             // safe just remove the whole task if it still exists.
-            mSsp.removeTask(taskId);
+            try {
+                mIam.removeTask(taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to get description for task=" + taskId);
+            }
         }
     }
 
@@ -96,7 +108,7 @@
         }
     }
 
-    private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
+    private final SysUiTaskStackChangeListener mLockListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onTaskProfileLocked(int taskId, int userId) {
             startWorkChallengeInTask(taskId, userId);
diff --git a/com/android/systemui/pip/phone/InputConsumerController.java b/com/android/systemui/pip/phone/InputConsumerController.java
index e6d6c55..db4f988 100644
--- a/com/android/systemui/pip/phone/InputConsumerController.java
+++ b/com/android/systemui/pip/phone/InputConsumerController.java
@@ -18,6 +18,8 @@
 
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 
+import android.os.Binder;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
@@ -77,7 +79,8 @@
         }
     }
 
-    private IWindowManager mWindowManager;
+    private final IWindowManager mWindowManager;
+    private final IBinder mToken;
 
     private PipInputEventReceiver mInputEventReceiver;
     private TouchListener mListener;
@@ -85,6 +88,7 @@
 
     public InputConsumerController(IWindowManager windowManager) {
         mWindowManager = windowManager;
+        mToken = new Binder();
         registerInputConsumer();
     }
 
@@ -122,7 +126,7 @@
             final InputChannel inputChannel = new InputChannel();
             try {
                 mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
-                mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel);
+                mWindowManager.createInputConsumer(mToken, INPUT_CONSUMER_PIP, inputChannel);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to create PIP input consumer", e);
             }
diff --git a/com/android/systemui/pip/phone/PipManager.java b/com/android/systemui/pip/phone/PipManager.java
index 7e87666..2963506 100644
--- a/com/android/systemui/pip/phone/PipManager.java
+++ b/com/android/systemui/pip/phone/PipManager.java
@@ -40,8 +40,9 @@
 import com.android.systemui.pip.BasePipManager;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.io.PrintWriter;
 
@@ -69,7 +70,7 @@
     /**
      * Handler for system task stack changes.
      */
-    TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+    SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             mTouchHandler.onActivityPinned();
@@ -173,7 +174,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
-        SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
 
         mInputConsumerController = new InputConsumerController(mWindowManager);
         mMediaController = new PipMediaController(context, mActivityManager);
@@ -202,9 +203,9 @@
                 StackInfo stackInfo = mActivityManager.getStackInfo(
                         WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                 if (stackInfo != null && stackInfo.taskIds != null) {
-                    SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
+                    ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
                     for (int taskId : stackInfo.taskIds) {
-                        ssp.cancelThumbnailTransition(taskId);
+                        am.cancelThumbnailTransition(taskId);
                     }
                 }
             } catch (RemoteException e) {
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index c92562b..eef43d2 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -45,8 +45,9 @@
 
 import com.android.systemui.R;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -234,7 +235,7 @@
 
         mActivityManager = ActivityManager.getService();
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
-        SystemServicesProxy.getInstance(context).registerTaskStackListener(mTaskStackListener);
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
         mContext.registerReceiver(mBroadcastReceiver, intentFilter);
@@ -620,7 +621,7 @@
         return false;
     }
 
-    private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
+    private SysUiTaskStackChangeListener mTaskStackListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
             if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
diff --git a/com/android/systemui/qs/QSFooterImpl.java b/com/android/systemui/qs/QSFooterImpl.java
index 3199dec..5ffd785 100644
--- a/com/android/systemui/qs/QSFooterImpl.java
+++ b/com/android/systemui/qs/QSFooterImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs;
 
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
 
 import android.app.ActivityManager;
@@ -37,7 +39,6 @@
 import android.view.View.OnClickListener;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -51,10 +52,11 @@
 import com.android.systemui.R;
 import com.android.systemui.R.dimen;
 import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.qs.TouchAnimator.Listener;
 import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.ExpandableIndicator;
 import com.android.systemui.statusbar.phone.MultiUserSwitch;
 import com.android.systemui.statusbar.phone.SettingsButton;
@@ -70,7 +72,7 @@
 
 public class QSFooterImpl extends FrameLayout implements QSFooter,
         NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
-        SignalCallback {
+        SignalCallback, CommandQueue.Callbacks {
     private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
 
     private ActivityStarter mActivityStarter;
@@ -83,6 +85,7 @@
     private View mAlarmStatusCollapsed;
     private View mDate;
 
+    private boolean mQsDisabled;
     private QSPanel mQsPanel;
 
     private boolean mExpanded;
@@ -278,9 +281,16 @@
     }
 
     @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+    }
+
+    @Override
     @VisibleForTesting
     public void onDetachedFromWindow() {
         setListening(false);
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
         super.onDetachedFromWindow();
     }
 
@@ -302,6 +312,14 @@
         return findViewById(R.id.expand_indicator);
     }
 
+    @Override
+    public void disable(int state1, int state2, boolean animate) {
+        final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+        if (disabled == mQsDisabled) return;
+        mQsDisabled = disabled;
+        updateEverything();
+    }
+
     public void updateEverything() {
         post(() -> {
             updateVisibilities();
@@ -311,11 +329,18 @@
 
     private void updateVisibilities() {
         updateAlarmVisibilities();
+
+        mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
                 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+
+        mExpandIndicator.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
+
         final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
 
-        mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
+
+        mMultiUserSwitch.setVisibility(mExpanded
+                && UserManager.get(mContext).isUserSwitcherEnabled()
                 ? View.VISIBLE : View.INVISIBLE);
 
         mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
diff --git a/com/android/systemui/qs/QuickQSPanel.java b/com/android/systemui/qs/QuickQSPanel.java
index 00b883a..947b23f 100644
--- a/com/android/systemui/qs/QuickQSPanel.java
+++ b/com/android/systemui/qs/QuickQSPanel.java
@@ -25,7 +25,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.plugins.qs.QSTileView;
@@ -43,6 +43,7 @@
 
     public static final String NUM_QUICK_TILES = "sysui_qqs_count";
 
+    private boolean mDisabledByPolicy;
     private int mMaxTiles;
     protected QSPanel mFullPanel;
 
@@ -151,6 +152,30 @@
         return Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, 6);
     }
 
+    void setDisabledByPolicy(boolean disabled) {
+        if (disabled != mDisabledByPolicy) {
+            mDisabledByPolicy = disabled;
+            setVisibility(disabled ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    /**
+     * Sets the visibility of this {@link QuickQSPanel}. This method has no effect when this panel
+     * is disabled by policy through {@link #setDisabledByPolicy(boolean)}, and in this case the
+     * visibility will always be {@link View#GONE}. This method is called externally by
+     * {@link QSAnimator} only.
+     */
+    @Override
+    public void setVisibility(int visibility) {
+        if (mDisabledByPolicy) {
+            if (getVisibility() == View.GONE) {
+                return;
+            }
+            visibility = View.GONE;
+        }
+        super.setVisibility(visibility);
+    }
+
     private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
 
         protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
diff --git a/com/android/systemui/qs/QuickStatusBarHeader.java b/com/android/systemui/qs/QuickStatusBarHeader.java
index 0709e22..398592a 100644
--- a/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -14,6 +14,9 @@
 
 package com.android.systemui.qs;
 
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -30,13 +33,14 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSDetail.Callback;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
 
-
-public class QuickStatusBarHeader extends RelativeLayout {
+public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue.Callbacks {
 
     private ActivityStarter mActivityStarter;
 
@@ -44,6 +48,7 @@
 
     private boolean mExpanded;
     private boolean mListening;
+    private boolean mQsDisabled;
 
     protected QuickQSPanel mHeaderQsPanel;
     protected QSTileHost mHost;
@@ -119,9 +124,25 @@
     }
 
     @Override
+    public void disable(int state1, int state2, boolean animate) {
+        final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+        if (disabled == mQsDisabled) return;
+        mQsDisabled = disabled;
+        mHeaderQsPanel.setDisabledByPolicy(disabled);
+        final int rawHeight = (int) getResources().getDimension(R.dimen.status_bar_header_height);
+        getLayoutParams().height = disabled ? (rawHeight - mHeaderQsPanel.getHeight()) : rawHeight;
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+    }
+
+    @Override
     @VisibleForTesting
     public void onDetachedFromWindow() {
         setListening(false);
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
         super.onDetachedFromWindow();
     }
 
diff --git a/com/android/systemui/qs/tiles/BluetoothTile.java b/com/android/systemui/qs/tiles/BluetoothTile.java
index 1aecdce..0e4a9fe 100644
--- a/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -134,7 +134,7 @@
                 if (lastDevice != null) {
                     int batteryLevel = lastDevice.getBatteryLevel();
                     if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                        state.icon = new BluetoothBatteryTileIcon(batteryLevel,
+                        state.icon = new BluetoothBatteryTileIcon(lastDevice,
                                 mContext.getResources().getFraction(
                                         R.fraction.bt_battery_scale_fraction, 1, 1));
                     }
@@ -213,18 +213,19 @@
     }
 
     private class BluetoothBatteryTileIcon extends Icon {
-        private int mLevel;
         private float mIconScale;
+        private CachedBluetoothDevice mDevice;
 
-        BluetoothBatteryTileIcon(int level, float iconScale) {
-            mLevel = level;
+        BluetoothBatteryTileIcon(CachedBluetoothDevice device, float iconScale) {
             mIconScale = iconScale;
+            mDevice = device;
         }
 
         @Override
         public Drawable getDrawable(Context context) {
-            return createLayerDrawable(context,
-                    R.drawable.ic_qs_bluetooth_connected, mLevel, mIconScale);
+            // This method returns Pair<Drawable, String> while first value is the drawable
+            return com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription(
+                    context, mDevice, mIconScale).first;
         }
     }
 
@@ -306,7 +307,7 @@
                         item.iconResId = R.drawable.ic_qs_bluetooth_connected;
                         int batteryLevel = device.getBatteryLevel();
                         if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                            item.icon = new BluetoothBatteryTileIcon(batteryLevel,
+                            item.icon = new BluetoothBatteryTileIcon(device,
                                     1 /* iconScale */);
                             item.line2 = mContext.getString(
                                     R.string.quick_settings_connected_battery_level,
diff --git a/com/android/systemui/qs/tiles/NightDisplayTile.java b/com/android/systemui/qs/tiles/NightDisplayTile.java
index 4c20361..763ffc6 100644
--- a/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -22,8 +22,7 @@
 import android.service.quicksettings.Tile;
 import android.widget.Switch;
 
-import com.android.internal.app.NightDisplayController;
-import com.android.internal.logging.MetricsLogger;
+import com.android.internal.app.ColorDisplayController;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSHost;
@@ -31,19 +30,19 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
 public class NightDisplayTile extends QSTileImpl<BooleanState>
-        implements NightDisplayController.Callback {
+        implements ColorDisplayController.Callback {
 
-    private NightDisplayController mController;
+    private ColorDisplayController mController;
     private boolean mIsListening;
 
     public NightDisplayTile(QSHost host) {
         super(host);
-        mController = new NightDisplayController(mContext, ActivityManager.getCurrentUser());
+        mController = new ColorDisplayController(mContext, ActivityManager.getCurrentUser());
     }
 
     @Override
     public boolean isAvailable() {
-        return NightDisplayController.isAvailable(mContext);
+        return ColorDisplayController.isAvailable(mContext);
     }
 
     @Override
@@ -65,7 +64,7 @@
         }
 
         // Make a new controller for the new user.
-        mController = new NightDisplayController(mContext, newUserId);
+        mController = new ColorDisplayController(mContext, newUserId);
         if (mIsListening) {
             mController.setListener(this);
         }
diff --git a/com/android/systemui/recents/Recents.java b/com/android/systemui/recents/Recents.java
index ce1438a..5b62c7d 100644
--- a/com/android/systemui/recents/Recents.java
+++ b/com/android/systemui/recents/Recents.java
@@ -63,6 +63,7 @@
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -246,7 +247,7 @@
             return;
         }
 
-        sSystemServicesProxy.sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+        ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
         int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
@@ -394,7 +395,7 @@
     }
 
     @Override
-    public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+    public boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
             int metricsDockAction) {
         // Ensure the device has been provisioned before allowing the user to interact with
         // recents
@@ -410,12 +411,12 @@
         }
 
         int currentUser = sSystemServicesProxy.getCurrentUser();
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+        ActivityManager.RunningTaskInfo runningTask =
+                ActivityManagerWrapper.getInstance().getRunningTask();
         final int activityType = runningTask != null
                 ? runningTask.configuration.windowConfiguration.getActivityType()
                 : ACTIVITY_TYPE_UNDEFINED;
-        boolean screenPinningActive = ssp.isScreenPinningActive();
+        boolean screenPinningActive = sSystemServicesProxy.isScreenPinningActive();
         boolean isRunningTaskInHomeOrRecentsStack =
                 activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
         if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
@@ -426,15 +427,16 @@
                             runningTask.topActivity.flattenToShortString());
                 }
                 if (sSystemServicesProxy.isSystemUser(currentUser)) {
-                    mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
+                    mImpl.splitPrimaryTask(runningTask.id, dragMode, stackCreateMode,
+                            initialBounds);
                 } else {
                     if (mSystemToUserCallbacks != null) {
                         IRecentsNonSystemUserCallbacks callbacks =
                                 mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
                         if (callbacks != null) {
                             try {
-                                callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode,
-                                        initialBounds);
+                                callbacks.splitPrimaryTask(runningTask.id, dragMode,
+                                        stackCreateMode, initialBounds);
                             } catch (RemoteException e) {
                                 Log.e(TAG, "Callback failed", e);
                             }
diff --git a/com/android/systemui/recents/RecentsActivity.java b/com/android/systemui/recents/RecentsActivity.java
index b75a142..9aecc68 100644
--- a/com/android/systemui/recents/RecentsActivity.java
+++ b/com/android/systemui/recents/RecentsActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.recents;
 
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY;
+
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.TaskStackBuilder;
@@ -92,7 +94,7 @@
 import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.RecentsView;
 import com.android.systemui.recents.views.SystemBarScrimViews;
-import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -280,8 +282,7 @@
                 new DismissRecentsToHomeAnimationStarted(animateTaskViews);
         dismissEvent.addPostAnimationCallback(new LaunchHomeRunnable(mHomeIntent,
                 overrideAnimation));
-        Recents.getSystemServices().sendCloseSystemWindows(
-                StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
+        ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
         EventBus.getDefault().send(dismissEvent);
     }
 
@@ -468,7 +469,7 @@
         if (launchState.launchedFromApp) {
             Task launchTarget = stack.getLaunchTarget();
             int launchTaskIndexInStack = launchTarget != null
-                    ? stack.indexOfStackTask(launchTarget)
+                    ? stack.indexOfTask(launchTarget)
                     : 0;
             MetricsLogger.count(this, "overview_source_app", 1);
             // If from an app, track the stack index of the app in the stack (for affiliated tasks)
@@ -514,7 +515,7 @@
 
         // Notify of the config change
         Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this);
-        int numStackTasks = mRecentsView.getStack().getStackTaskCount();
+        int numStackTasks = mRecentsView.getStack().getTaskCount();
         EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
                 mLastConfig.orientation != newDeviceConfiguration.orientation,
                 mLastConfig.densityDpi != newDeviceConfiguration.densityDpi, numStackTasks > 0));
@@ -706,9 +707,9 @@
         int launchToTaskId = launchState.launchedToTaskId;
         if (launchToTaskId != -1 &&
                 (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) {
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            ssp.cancelWindowTransition(launchState.launchedToTaskId);
-            ssp.cancelThumbnailTransition(getTaskId());
+            ActivityManagerWrapper am = ActivityManagerWrapper.getInstance();
+            am.cancelWindowTransition(launchState.launchedToTaskId);
+            am.cancelThumbnailTransition(getTaskId());
         }
     }
 
@@ -755,8 +756,7 @@
         loader.deleteTaskData(event.task, false);
 
         // Remove the task from activity manager
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ssp.removeTask(event.task.key.id);
+        ActivityManagerWrapper.getInstance().removeTask(event.task.key.id);
     }
 
     public final void onBusEvent(TaskViewDismissedEvent event) {
@@ -824,7 +824,7 @@
         loader.loadTasks(loadPlan, loadOpts);
 
         TaskStack stack = loadPlan.getTaskStack();
-        int numStackTasks = stack.getStackTaskCount();
+        int numStackTasks = stack.getTaskCount();
         boolean showDeferredAnimation = numStackTasks > 0;
 
         if (sendConfigChangedEvent) {
diff --git a/com/android/systemui/recents/RecentsActivityLaunchState.java b/com/android/systemui/recents/RecentsActivityLaunchState.java
index d2326ce..14fda95 100644
--- a/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -50,24 +50,4 @@
         launchedViaDragGesture = false;
         launchedViaDockGesture = false;
     }
-
-    /**
-     * Returns the task to focus given the current launch state.
-     */
-    public int getInitialFocusTaskIndex(int numTasks, boolean useGridLayout) {
-        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
-        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
-        if (launchedFromApp) {
-            if (useGridLayout) {
-                // If coming from another app to the grid layout, focus the front most task
-                return numTasks - 1;
-            }
-
-            // If coming from another app, focus the next task
-            return Math.max(0, numTasks - 2);
-        } else {
-            // If coming from home, focus the front most task
-            return numTasks - 1;
-        }
-    }
 }
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index 868ed64..3b1b2f9 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -20,14 +20,16 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.view.View.MeasureSpec;
 
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
+
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -38,7 +40,6 @@
 import android.util.Log;
 import android.util.MutableBoolean;
 import android.util.Pair;
-import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
@@ -70,26 +71,29 @@
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ForegroundThread;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.shared.recents.model.RecentsTaskLoader;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.recents.views.RecentsTransitionHelper;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
 import com.android.systemui.recents.views.TaskStackView;
 import com.android.systemui.recents.views.TaskViewHeader;
 import com.android.systemui.recents.views.TaskViewTransform;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.DividerView;
 import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * An implementation of the Recents component for the current user.  For secondary users, this can
@@ -113,10 +117,10 @@
     public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
 
     /**
-     * An implementation of TaskStackChangeListener, that allows us to listen for changes to the system
+     * An implementation of SysUiTaskStackChangeListener, that allows us to listen for changes to the system
      * task stacks and update recents accordingly.
      */
-    class TaskStackListenerImpl extends TaskStackChangeListener {
+    class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
 
         @Override
         public void onTaskStackChangedBackground() {
@@ -134,8 +138,8 @@
                 }
 
                 // Load the next task only if we aren't svelte
-                SystemServicesProxy ssp = Recents.getSystemServices();
-                ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
+                ActivityManager.RunningTaskInfo runningTaskInfo =
+                        ActivityManagerWrapper.getInstance().getRunningTask();
                 RecentsTaskLoader loader = Recents.getTaskLoader();
                 RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
                 loader.preloadTasks(plan, -1);
@@ -155,7 +159,7 @@
                     mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
                     VisibilityReport visibilityReport =
                             mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
-                                    stack.getStackTasks());
+                                    stack.getTasks());
 
                     launchOpts.runningTaskId = runningTaskInfo != null ? runningTaskInfo.id : -1;
                     launchOpts.numVisibleTasks = visibilityReport.numVisibleTasks;
@@ -196,14 +200,13 @@
         }
 
         @Override
-        public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
+        public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
             // Check this is for the right user
             if (!checkCurrentUserId(mContext, false /* debug */)) {
                 return;
             }
 
-            EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId,
-                    new ThumbnailData(snapshot)));
+            EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, snapshot));
         }
     }
 
@@ -217,13 +220,12 @@
     // recents. In this case, we defer the toggle state until then and apply it immediately after.
     private static boolean mToggleFollowingTransitionStart = true;
 
-    private ActivityOptions.OnAnimationStartedListener mResetToggleFlagListener =
-            new OnAnimationStartedListener() {
-                @Override
-                public void onAnimationStarted() {
-                    setWaitingForTransitionStart(false);
-                }
-            };
+    private Runnable mResetToggleFlagListener = new Runnable() {
+        @Override
+        public void run() {
+            setWaitingForTransitionStart(false);
+        }
+    };
 
     protected Context mContext;
     protected Handler mHandler;
@@ -266,8 +268,7 @@
 
         // Register the task stack listener
         mTaskStackListener = new TaskStackListenerImpl();
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ssp.registerTaskStackListener(mTaskStackListener);
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
 
         // Initialize the static configuration resources
         mDummyStackView = new TaskStackView(mContext);
@@ -349,7 +350,8 @@
             boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
             MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible);
             if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
-                ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+                ActivityManager.RunningTaskInfo runningTask =
+                        ActivityManagerWrapper.getInstance().getRunningTask();
                 startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
                         growTarget);
             }
@@ -440,12 +442,14 @@
                 }
 
                 // Otherwise, start the recents activity
-                ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+                ActivityManager.RunningTaskInfo runningTask =
+                        ActivityManagerWrapper.getInstance().getRunningTask();
                 startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
                         growTarget);
 
                 // Only close the other system windows if we are actually showing recents
-                ssp.sendCloseSystemWindows(StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
+                ActivityManagerWrapper.getInstance().closeSystemWindows(
+                        SYSTEM_DIALOG_REASON_RECENT_APPS);
                 mLastToggleTime = SystemClock.elapsedRealtime();
             }
         } catch (ActivityNotFoundException e) {
@@ -465,7 +469,8 @@
         // don't block the touch feedback on the nav bar button which triggers this.
         mHandler.post(() -> {
             if (!ssp.isRecentsActivityVisible(null)) {
-                ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+                ActivityManager.RunningTaskInfo runningTask =
+                        ActivityManagerWrapper.getInstance().getRunningTask();
                 if (runningTask == null) {
                     return;
                 }
@@ -519,14 +524,15 @@
         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
 
         // Return early if there is no running task
-        ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+        ActivityManager.RunningTaskInfo runningTask =
+                ActivityManagerWrapper.getInstance().getRunningTask();
         if (runningTask == null) return;
 
         // Find the task in the recents list
         boolean isRunningTaskInHomeStack =
                 runningTask.configuration.windowConfiguration.getActivityType()
                         == ACTIVITY_TYPE_HOME;
-        ArrayList<Task> tasks = focusedStack.getStackTasks();
+        ArrayList<Task> tasks = focusedStack.getTasks();
         Task toTask = null;
         ActivityOptions launchOpts = null;
         int taskCount = tasks.size();
@@ -556,8 +562,8 @@
         }
 
         // Launch the task
-        ssp.startActivityFromRecents(
-                mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
+        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts,
+                null /* resultCallback */, null /* resultCallbackHandler */);
     }
 
     /**
@@ -574,14 +580,15 @@
         if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
 
         // Return early if there is no running task (can't determine affiliated tasks in this case)
-        ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+        ActivityManager.RunningTaskInfo runningTask =
+                ActivityManagerWrapper.getInstance().getRunningTask();
         final int activityType = runningTask.configuration.windowConfiguration.getActivityType();
         if (runningTask == null) return;
         // Return early if the running task is in the home/recents stack (optimization)
         if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return;
 
         // Find the task in the recents list
-        ArrayList<Task> tasks = focusedStack.getStackTasks();
+        ArrayList<Task> tasks = focusedStack.getTasks();
         Task toTask = null;
         ActivityOptions launchOpts = null;
         int taskCount = tasks.size();
@@ -625,8 +632,8 @@
         MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
 
         // Launch the task
-        ssp.startActivityFromRecents(
-                mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
+        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(toTask.key, launchOpts,
+                null /* resultListener */, null /* resultCallbackHandler */);
     }
 
     public void showNextAffiliatedTask() {
@@ -641,13 +648,13 @@
         showRelativeAffiliatedTask(false);
     }
 
-    public void dockTopTask(int topTaskId, int dragMode,
-            int stackCreateMode, Rect initialBounds) {
+    public void splitPrimaryTask(int taskId, int dragMode, int stackCreateMode,
+            Rect initialBounds) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Make sure we inform DividerView before we actually start the activity so we can change
         // the resize mode already.
-        if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
+        if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
             EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
             showRecents(
                     false /* triggeredFromAltTab */,
@@ -726,7 +733,10 @@
         // However, the window bounds include the insets, so we need to subtract them here to make
         // them identical.
         if (ssp.hasDockedTask()) {
-            windowRect.bottom -= systemInsets.bottom;
+            if (systemInsets.bottom < windowRect.height()) {
+                // Only apply inset if it isn't going to cause the rect height to go negative.
+                windowRect.bottom -= systemInsets.bottom;
+            }
             systemInsets.bottom = 0;
         }
         calculateWindowStableInsets(systemInsets, windowRect, displayRect);
@@ -864,22 +874,22 @@
                 windowOverrideRect);
 
         RectF toTaskRect = toTransform.rect;
-        AppTransitionAnimationSpecsFuture future =
-                new RecentsTransitionHelper(mContext).getAppTransitionFuture(
-                        () -> {
-                    Rect rect = new Rect();
-                    toTaskRect.round(rect);
-                    GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
-                            toTransform);
-                    return Lists.newArrayList(new AppTransitionAnimationSpec(
-                            toTask.key.id, thumbnail, rect));
-                });
+        AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(mHandler) {
+            @Override
+            public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+                Rect rect = new Rect();
+                toTaskRect.round(rect);
+                Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+                return Lists.newArrayList(new AppTransitionAnimationSpecCompat(toTask.key.id,
+                        thumbnail, rect));
+            }
+        };
 
         // For low end ram devices, wait for transition flag is reset when Recents entrance
         // animation is complete instead of when the transition animation starts
-        return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
-                mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
-                false /* scaleUp */), future);
+        return new Pair<>(RecentsTransition.createAspectScaleAnimation(mContext, mHandler,
+                false /* scaleUp */, future, isLowRamDevice ? null : mResetToggleFlagListener),
+                future);
     }
 
     /**
@@ -894,7 +904,7 @@
             runningTaskOut.copyFrom(launchTask);
         } else {
             // If no task is specified or we can not find the task just use the front most one
-            launchTask = stack.getStackFrontMostTask();
+            launchTask = stack.getFrontMostTask();
             runningTaskOut.copyFrom(launchTask);
         }
 
@@ -909,7 +919,7 @@
     /**
      * Draws the header of a task used for the window animation into a bitmap.
      */
-    private GraphicBuffer drawThumbnailTransitionBitmap(Task toTask,
+    private Bitmap drawThumbnailTransitionBitmap(Task toTask,
             TaskViewTransform toTransform) {
         SystemServicesProxy ssp = Recents.getSystemServices();
         int width = (int) toTransform.rect.width();
@@ -919,7 +929,7 @@
                 boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
                 mHeaderBar.onTaskViewSizeChanged(width, height);
                 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-                    return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+                    return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight,
                             null, 1f, 0xFFff0000);
                 } else {
                     // Workaround for b/27815919, reset the callback so that we do not trigger an
@@ -932,7 +942,7 @@
                             disabledInSafeMode);
                     mHeaderBar.onTaskDataLoaded();
                     mHeaderBar.setDimAlpha(toTransform.dimAlpha);
-                    return RecentsTransitionHelper.drawViewIntoGraphicBuffer(width, mTaskBarHeight,
+                    return RecentsTransition.drawViewIntoHardwareBitmap(width, mTaskBarHeight,
                             mHeaderBar, 1f, 0);
                 }
             }
@@ -1047,7 +1057,7 @@
             Recents.getSystemServices().startActivityAsUserAsync(intent, opts);
             EventBus.getDefault().send(new RecentsActivityStartingEvent());
             if (future != null) {
-                future.precacheSpecs();
+                future.composeSpecsSynchronous();
             }
         });
         EventBus.getDefault().send(hideMenuEvent);
diff --git a/com/android/systemui/recents/RecentsImplProxy.java b/com/android/systemui/recents/RecentsImplProxy.java
index ff9e89e..9493c78 100644
--- a/com/android/systemui/recents/RecentsImplProxy.java
+++ b/com/android/systemui/recents/RecentsImplProxy.java
@@ -90,7 +90,7 @@
     }
 
     @Override
-    public void dockTopTask(int topTaskId, int dragMode, int stackCreateMode,
+    public void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode,
             Rect initialBounds) throws RemoteException {
         SomeArgs args = SomeArgs.obtain();
         args.argi1 = topTaskId;
@@ -144,7 +144,7 @@
                     break;
                 case MSG_DOCK_TOP_TASK:
                     args = (SomeArgs) msg.obj;
-                    mImpl.dockTopTask(args.argi1, args.argi2, args.argi3 = 0,
+                    mImpl.splitPrimaryTask(args.argi1, args.argi2, args.argi3 = 0,
                             (Rect) args.arg1);
                     break;
                 case MSG_ON_DRAGGING_IN_RECENTS:
diff --git a/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java b/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
new file mode 100644
index 0000000..5d7f1ba
--- /dev/null
+++ b/com/android/systemui/recents/misc/SysUiTaskStackChangeListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.misc;
+
+import android.content.Context;
+
+import com.android.systemui.shared.system.TaskStackChangeListener;
+
+/**
+ * An implementation of {@link TaskStackChangeListener}.
+ */
+public abstract class SysUiTaskStackChangeListener extends TaskStackChangeListener {
+
+    /**
+     * Checks that the current user matches the user's SystemUI process.
+     */
+    protected final boolean checkCurrentUserId(Context context, boolean debug) {
+        int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
+        return checkCurrentUserId(currentUserId, debug);
+    }
+}
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index 55ec5e7..d89bab7 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -79,9 +79,11 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImpl;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.policy.UserInfoController;
 
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Acts as a shim around the real system services that we need to access data from, and provides
@@ -112,7 +114,6 @@
     UserManager mUm;
     Display mDisplay;
     String mRecentsPackage;
-    private TaskStackChangeListeners mTaskStackChangeListeners;
     private int mCurrentUserId;
 
     boolean mIsSafeMode;
@@ -122,7 +123,6 @@
     Paint mBgProtectionPaint;
     Canvas mBgProtectionCanvas;
 
-    private final Handler mHandler = new Handler();
     private final Runnable mGcRunnable = new Runnable() {
         @Override
         public void run() {
@@ -155,7 +155,6 @@
         mRecentsPackage = context.getPackageName();
         mIsSafeMode = mPm.isSafeMode();
         mCurrentUserId = mAm.getCurrentUser();
-        mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
 
         // Get the dummy thumbnail width/heights
         Resources res = context.getResources();
@@ -196,24 +195,6 @@
     }
 
     /**
-     * Returns the top running task.
-     */
-    public ActivityManager.RunningTaskInfo getRunningTask() {
-        // Note: The set of running tasks from the system is ordered by recency
-        try {
-            List<ActivityManager.RunningTaskInfo> tasks = mIam.getFilteredTasks(1,
-                    ACTIVITY_TYPE_RECENTS /* ignoreActivityType */,
-                    WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
-            if (tasks.isEmpty()) {
-                return null;
-            }
-            return tasks.get(0);
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    /**
      * Returns whether the recents activity is currently visible.
      */
     public boolean isRecentsActivityVisible() {
@@ -225,6 +206,8 @@
      *
      * @param isHomeStackVisible if provided, will return whether the home stack is visible
      *                           regardless of the recents visibility
+     *
+     * TODO(winsonc): Refactor this check to just use the recents activity lifecycle
      */
     public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) {
         if (mIam == null) return false;
@@ -239,13 +222,13 @@
                 final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
                 final int activityType = winConfig.getActivityType();
                 final int windowingMode = winConfig.getWindowingMode();
-                if (activityType == ACTIVITY_TYPE_HOME) {
+                if (homeStackInfo == null && activityType == ACTIVITY_TYPE_HOME) {
                     homeStackInfo = stackInfo;
-                } else if (activityType == ACTIVITY_TYPE_STANDARD
+                } else if (fullscreenStackInfo == null && activityType == ACTIVITY_TYPE_STANDARD
                         && (windowingMode == WINDOWING_MODE_FULLSCREEN
                             || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
                     fullscreenStackInfo = stackInfo;
-                } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+                } else if (recentsStackInfo == null && activityType == ACTIVITY_TYPE_RECENTS) {
                     recentsStackInfo = stackInfo;
                 }
             }
@@ -291,7 +274,7 @@
 
         try {
             final ActivityOptions options = ActivityOptions.makeBasic();
-            options.setDockCreateMode(createMode);
+            options.setSplitScreenCreateMode(createMode);
             options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
             mIam.startActivityFromRecents(taskId, options.toBundle());
             return true;
@@ -301,14 +284,15 @@
         return false;
     }
 
-    /** Docks an already resumed task to the side of the screen. */
-    public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) {
+    /** Moves an already resumed task to the side of the screen to initiate split screen. */
+    public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
+            Rect initialBounds) {
         if (mIam == null) {
             return false;
         }
 
         try {
-            return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */,
+            return mIam.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
                     false /* animate */, initialBounds);
         } catch (RemoteException e) {
             e.printStackTrace();
@@ -363,32 +347,6 @@
         return insets.right > 0;
     }
 
-    /**
-     * Cancels the current window transtion to/from Recents for the given task id.
-     */
-    public void cancelWindowTransition(int taskId) {
-        if (mIam == null) return;
-
-        try {
-            mIam.cancelTaskWindowTransition(taskId);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Cancels the current thumbnail transtion to/from Recents for the given task id.
-     */
-    public void cancelThumbnailTransition(int taskId) {
-        if (mIam == null) return;
-
-        try {
-            mIam.cancelTaskThumbnailTransition(taskId);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        }
-    }
-
     /** Set the task's windowing mode. */
     public void setTaskWindowingMode(int taskId, int windowingMode) {
         if (mIam == null) return;
@@ -400,40 +358,6 @@
         }
     }
 
-    /** Removes the task */
-    public void removeTask(final int taskId) {
-        if (mAm == null) return;
-
-        // Remove the task.
-        mUiOffloadThread.submit(() -> {
-            try {
-                mIam.removeTask(taskId);
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-        });
-    }
-
-    /**
-     * Sends a message to close other system windows.
-     */
-    public void sendCloseSystemWindows(String reason) {
-        mUiOffloadThread.submit(() -> {
-            try {
-                mIam.closeSystemDialogs(reason);
-            } catch (RemoteException e) {
-            }
-        });
-    }
-
-    public ActivityManager.TaskDescription getTaskDescription(int taskId) {
-        try {
-            return mIam.getTaskDescription(taskId);
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
     /**
      * Returns whether the provided {@param userId} represents the system user.
      */
@@ -556,56 +480,6 @@
                 opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
     }
 
-    public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
-            ActivityOptions options,
-            @Nullable final StartActivityFromRecentsResultListener resultListener) {
-        startActivityFromRecents(context, taskKey, taskName, options,
-                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED, resultListener);
-    }
-
-    /** Starts an activity from recents. */
-    public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
-            ActivityOptions options, int windowingMode, int activityType,
-            @Nullable final StartActivityFromRecentsResultListener resultListener) {
-        if (mIam == null) {
-            return;
-        }
-        if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            // We show non-visible docked tasks in Recents, but we always want to launch
-            // them in the fullscreen stack.
-            if (options == null) {
-                options = ActivityOptions.makeBasic();
-            }
-            options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-        } else if (windowingMode != WINDOWING_MODE_UNDEFINED
-                || activityType != ACTIVITY_TYPE_UNDEFINED) {
-            if (options == null) {
-                options = ActivityOptions.makeBasic();
-            }
-            options.setLaunchWindowingMode(windowingMode);
-            options.setLaunchActivityType(activityType);
-        }
-        final ActivityOptions finalOptions = options;
-
-        // Execute this from another thread such that we can do other things (like caching the
-        // bitmap for the thumbnail) while AM is busy starting our activity.
-        mUiOffloadThread.submit(() -> {
-            try {
-                mIam.startActivityFromRecents(
-                        taskKey.id, finalOptions == null ? null : finalOptions.toBundle());
-                if (resultListener != null) {
-                    mHandler.post(() -> resultListener.onStartActivityResult(true));
-                }
-            } catch (Exception e) {
-                Log.e(TAG, context.getString(
-                        R.string.recents_launch_error_message, taskName), e);
-                if (resultListener != null) {
-                    mHandler.post(() -> resultListener.onStartActivityResult(false));
-                }
-            }
-        });
-    }
-
     /** Starts an in-place animation on the front most application windows. */
     public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts) {
         if (mIam == null) return;
@@ -618,18 +492,6 @@
         }
     }
 
-    /**
-     * Registers a task stack listener with the system.
-     * This should be called on the main thread.
-     */
-    public void registerTaskStackListener(TaskStackChangeListener listener) {
-        if (mIam == null) return;
-
-        synchronized (mTaskStackChangeListeners) {
-            mTaskStackChangeListeners.addListener(mIam, listener);
-        }
-    }
-
     public void endProlongedAnimations() {
         if (mWm == null) {
             return;
diff --git a/com/android/systemui/recents/views/DockState.java b/com/android/systemui/recents/views/DockState.java
index 59f2868..65b96fb 100644
--- a/com/android/systemui/recents/views/DockState.java
+++ b/com/android/systemui/recents/views/DockState.java
@@ -16,8 +16,8 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.view.WindowManager.DOCKED_BOTTOM;
 import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_LEFT;
@@ -71,19 +71,19 @@
     public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
             null, null, null);
     public static final DockState LEFT = new DockState(DOCKED_LEFT,
-            DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
+            SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
             new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
             new RectF(0, 0, 0.5f, 1));
     public static final DockState TOP = new DockState(DOCKED_TOP,
-            DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+            SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
             new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
             new RectF(0, 0, 1, 0.5f));
     public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
-            DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
+            SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
             new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
             new RectF(0.5f, 0, 1, 1));
     public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
-            DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+            SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
             new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
             new RectF(0, 0.5f, 1, 1));
 
diff --git a/com/android/systemui/recents/views/RecentsTransitionComposer.java b/com/android/systemui/recents/views/RecentsTransitionComposer.java
new file mode 100644
index 0000000..1c47430
--- /dev/null
+++ b/com/android/systemui/recents/views/RecentsTransitionComposer.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.util.Log;
+
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A helper class to create the transition app animation specs to/from Recents
+ */
+public class RecentsTransitionComposer {
+
+    private static final String TAG = "RecentsTransitionComposer";
+
+    private Context mContext;
+    private TaskViewTransform mTmpTransform = new TaskViewTransform();
+
+    public RecentsTransitionComposer(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Composes a single animation spec for the given {@link TaskView}
+     */
+    private static AppTransitionAnimationSpecCompat composeAnimationSpec(TaskStackView stackView,
+            TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
+        Bitmap b = null;
+        if (addHeaderBitmap) {
+            b = composeHeaderBitmap(taskView, transform);
+            if (b == null) {
+                return null;
+            }
+        }
+
+        Rect taskRect = new Rect();
+        transform.rect.round(taskRect);
+        // Disable in for low ram devices because each task does in Recents does not have fullscreen
+        // height (stackView height) and when transitioning to fullscreen app, the code below would
+        // force the task thumbnail to full stackView height immediately causing the transition
+        // jarring.
+        if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
+                stackView.getStack().getFrontMostTask()) {
+            taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
+        }
+        return new AppTransitionAnimationSpecCompat(taskView.getTask().key.id, b, taskRect);
+    }
+
+    /**
+     * Composes the transition spec when docking a task, which includes a full task bitmap.
+     */
+    public List<AppTransitionAnimationSpecCompat> composeDockAnimationSpec(TaskView taskView,
+            Rect bounds) {
+        mTmpTransform.fillIn(taskView);
+        Task task = taskView.getTask();
+        Bitmap buffer = RecentsTransitionComposer.composeTaskBitmap(taskView, mTmpTransform);
+        return Collections.singletonList(new AppTransitionAnimationSpecCompat(task.key.id, buffer,
+                bounds));
+    }
+
+    /**
+     * Composes the animation specs for all the tasks in the target stack.
+     */
+    public List<AppTransitionAnimationSpecCompat> composeAnimationSpecs(final Task task,
+            final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
+        // Calculate the offscreen task rect (for tasks that are not backed by views)
+        TaskView taskView = stackView.getChildViewForTask(task);
+        TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
+        Rect offscreenTaskRect = new Rect();
+        stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
+
+        // If this is a full screen stack, the transition will be towards the single, full screen
+        // task. We only need the transition spec for this task.
+
+        // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
+        // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
+        if (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                || activityType == ACTIVITY_TYPE_ASSISTANT
+                || windowingMode == WINDOWING_MODE_UNDEFINED) {
+            List<AppTransitionAnimationSpecCompat> specs = new ArrayList<>();
+            if (taskView == null) {
+                specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
+            } else {
+                mTmpTransform.fillIn(taskView);
+                stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
+                AppTransitionAnimationSpecCompat spec = composeAnimationSpec(stackView, taskView,
+                        mTmpTransform, true /* addHeaderBitmap */);
+                if (spec != null) {
+                    specs.add(spec);
+                }
+            }
+            return specs;
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Composes a single animation spec for the given {@link Task}
+     */
+    private static AppTransitionAnimationSpecCompat composeOffscreenAnimationSpec(Task task,
+            Rect taskRect) {
+        return new AppTransitionAnimationSpecCompat(task.key.id, null, taskRect);
+    }
+
+    public static Bitmap composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
+        float scale = transform.scale;
+        int fromWidth = (int) (transform.rect.width() * scale);
+        int fromHeight = (int) (transform.rect.height() * scale);
+        if (fromWidth == 0 || fromHeight == 0) {
+            Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
+                    " at transform: " + transform);
+
+            return RecentsTransition.drawViewIntoHardwareBitmap(1, 1, null, 1f, 0x00ffffff);
+        } else {
+            if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+                return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, null, 1f,
+                        0xFFff0000);
+            } else {
+                return RecentsTransition.drawViewIntoHardwareBitmap(fromWidth, fromHeight, taskView,
+                        scale, 0);
+            }
+        }
+    }
+
+    private static Bitmap composeHeaderBitmap(TaskView taskView,
+            TaskViewTransform transform) {
+        float scale = transform.scale;
+        int headerWidth = (int) (transform.rect.width());
+        int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
+        if (headerWidth == 0 || headerHeight == 0) {
+            return null;
+        }
+
+        if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
+            return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight, null, 1f,
+                    0xFFff0000);
+        } else {
+            return RecentsTransition.drawViewIntoHardwareBitmap(headerWidth, headerHeight,
+                    taskView.mHeaderView, scale, 0);
+        }
+    }
+}
diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java
deleted file mode 100644
index 7442904..0000000
--- a/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ /dev/null
@@ -1,458 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.AppTransitionAnimationSpec;
-import android.view.DisplayListCanvas;
-import android.view.IAppTransitionAnimationSpecsFuture;
-import android.view.RenderNode;
-import android.view.ThreadedRenderer;
-import android.view.View;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
-import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A helper class to create transitions to/from Recents
- */
-public class RecentsTransitionHelper {
-
-    private static final String TAG = "RecentsTransitionHelper";
-    private static final boolean DEBUG = false;
-
-    /**
-     * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently
-     * waiting for the specs to be retrieved.
-     */
-    private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>();
-
-    @GuardedBy("this")
-    private List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
-
-    private Context mContext;
-    private Handler mHandler;
-    private TaskViewTransform mTmpTransform = new TaskViewTransform();
-
-    private class StartScreenPinningRunnableRunnable implements Runnable {
-
-        private int taskId = -1;
-
-        @Override
-        public void run() {
-            EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext, taskId));
-        }
-    }
-    private StartScreenPinningRunnableRunnable mStartScreenPinningRunnable
-            = new StartScreenPinningRunnableRunnable();
-
-    public RecentsTransitionHelper(Context context) {
-        mContext = context;
-        mHandler = new Handler();
-    }
-
-    /**
-     * Launches the specified {@link Task}.
-     */
-    public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
-            final TaskStackView stackView, final TaskView taskView,
-            final boolean screenPinningRequested, final int windowingMode, final int activityType) {
-
-        final ActivityOptions.OnAnimationStartedListener animStartedListener;
-        final AppTransitionAnimationSpecsFuture transitionFuture;
-        if (taskView != null) {
-
-            // Fetch window rect here already in order not to be blocked on lock contention in WM
-            // when the future calls it.
-            final Rect windowRect = Recents.getSystemServices().getWindowRect();
-            transitionFuture = getAppTransitionFuture(() -> composeAnimationSpecs(
-                    task, stackView, windowingMode, activityType, windowRect));
-            animStartedListener = new OnAnimationStartedListener() {
-                private boolean mHandled;
-
-                @Override
-                public void onAnimationStarted() {
-                    if (mHandled) {
-                        return;
-                    }
-                    mHandled = true;
-
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                    stackView.cancelAllTaskViewAnimations();
-
-                    if (screenPinningRequested) {
-                        // Request screen pinning after the animation runs
-                        mStartScreenPinningRunnable.taskId = task.key.id;
-                        mHandler.postDelayed(mStartScreenPinningRunnable, 350);
-                    }
-
-                    if (!Recents.getConfiguration().isLowRamDevice) {
-                        // Reset the state where we are waiting for the transition to start
-                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
-                    }
-                }
-            };
-        } else {
-            // This is only the case if the task is not on screen (scrolled offscreen for example)
-            transitionFuture = null;
-            animStartedListener = new OnAnimationStartedListener() {
-                private boolean mHandled;
-
-                @Override
-                public void onAnimationStarted() {
-                    if (mHandled) {
-                        return;
-                    }
-                    mHandled = true;
-
-                    // If we are launching into another task, cancel the previous task's
-                    // window transition
-                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
-                    stackView.cancelAllTaskViewAnimations();
-
-                    if (!Recents.getConfiguration().isLowRamDevice) {
-                        // Reset the state where we are waiting for the transition to start
-                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
-                    }
-                }
-            };
-        }
-
-        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
-        final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
-                mHandler, transitionFuture != null ? transitionFuture.future : null,
-                animStartedListener, true /* scaleUp */);
-        if (taskView == null) {
-            // If there is no task view, then we do not need to worry about animating out occluding
-            // task views, and we can launch immediately
-            startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                    windowingMode, activityType);
-        } else {
-            LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
-                    screenPinningRequested);
-            EventBus.getDefault().send(launchStartedEvent);
-            startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
-                    activityType);
-        }
-        Recents.getSystemServices().sendCloseSystemWindows(
-                StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
-    }
-
-    public IRemoteCallback wrapStartedListener(final OnAnimationStartedListener listener) {
-        if (listener == null) {
-            return null;
-        }
-        return new IRemoteCallback.Stub() {
-            @Override
-            public void sendResult(Bundle data) throws RemoteException {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        listener.onAnimationStarted();
-                    }
-                });
-            }
-        };
-    }
-
-    /**
-     * Starts the activity for the launch task.
-     *
-     * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
-     *                 we are toggling recents and the launch-to task is now offscreen.
-     */
-    private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
-            ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
-            int windowingMode, int activityType) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
-                activityType,
-                succeeded -> {
-            if (succeeded) {
-                // Keep track of the index of the task launch
-                int taskIndexFromFront = 0;
-                int taskIndex = stack.indexOfStackTask(task);
-                if (taskIndex > -1) {
-                    taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
-                }
-                EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
-            } else {
-                // Dismiss the task if we fail to launch it
-                if (taskView != null) {
-                    taskView.dismissTask();
-                }
-
-                // Keep track of failed launches
-                EventBus.getDefault().send(new LaunchTaskFailedEvent());
-            }
-        });
-        if (transitionFuture != null) {
-            mHandler.post(transitionFuture::precacheSpecs);
-        }
-    }
-
-    /**
-     * Creates a future which will later be queried for animation specs for this current transition.
-     *
-     * @param composer The implementation that composes the specs on the UI thread.
-     */
-    public AppTransitionAnimationSpecsFuture getAppTransitionFuture(
-            final AnimationSpecComposer composer) {
-        synchronized (this) {
-            mAppTransitionAnimationSpecs = SPECS_WAITING;
-        }
-        IAppTransitionAnimationSpecsFuture future = new IAppTransitionAnimationSpecsFuture.Stub() {
-            @Override
-            public AppTransitionAnimationSpec[] get() throws RemoteException {
-                mHandler.post(() -> {
-                    synchronized (RecentsTransitionHelper.this) {
-                        mAppTransitionAnimationSpecs = composer.composeSpecs();
-                        RecentsTransitionHelper.this.notifyAll();
-                    }
-                });
-                synchronized (RecentsTransitionHelper.this) {
-                    while (mAppTransitionAnimationSpecs == SPECS_WAITING) {
-                        try {
-                            RecentsTransitionHelper.this.wait();
-                        } catch (InterruptedException e) {}
-                    }
-                    if (mAppTransitionAnimationSpecs == null) {
-                        return null;
-                    }
-                    AppTransitionAnimationSpec[] specs
-                            = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
-                    mAppTransitionAnimationSpecs.toArray(specs);
-                    mAppTransitionAnimationSpecs = SPECS_WAITING;
-                    return specs;
-                }
-            }
-        };
-        return new AppTransitionAnimationSpecsFuture(composer, future);
-    }
-
-    /**
-     * Composes the transition spec when docking a task, which includes a full task bitmap.
-     */
-    public List<AppTransitionAnimationSpec> composeDockAnimationSpec(TaskView taskView,
-            Rect bounds) {
-        mTmpTransform.fillIn(taskView);
-        Task task = taskView.getTask();
-        GraphicBuffer buffer = RecentsTransitionHelper.composeTaskBitmap(taskView, mTmpTransform);
-        return Collections.singletonList(new AppTransitionAnimationSpec(task.key.id, buffer,
-                bounds));
-    }
-
-    /**
-     * Composes the animation specs for all the tasks in the target stack.
-     */
-    private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
-            final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
-        // Calculate the offscreen task rect (for tasks that are not backed by views)
-        TaskView taskView = stackView.getChildViewForTask(task);
-        TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
-        Rect offscreenTaskRect = new Rect();
-        stackLayout.getFrontOfStackTransform().rect.round(offscreenTaskRect);
-
-        // If this is a full screen stack, the transition will be towards the single, full screen
-        // task. We only need the transition spec for this task.
-
-        // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
-        // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
-        if (windowingMode == WINDOWING_MODE_FULLSCREEN
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
-                || activityType == ACTIVITY_TYPE_ASSISTANT
-                || windowingMode == WINDOWING_MODE_UNDEFINED) {
-            List<AppTransitionAnimationSpec> specs = new ArrayList<>();
-            if (taskView == null) {
-                specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
-            } else {
-                mTmpTransform.fillIn(taskView);
-                stackLayout.transformToScreenCoordinates(mTmpTransform, windowRect);
-                AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, taskView,
-                        mTmpTransform, true /* addHeaderBitmap */);
-                if (spec != null) {
-                    specs.add(spec);
-                }
-            }
-            return specs;
-        }
-        return Collections.emptyList();
-    }
-
-    /**
-     * Composes a single animation spec for the given {@link Task}
-     */
-    private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
-            Rect taskRect) {
-        return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
-    }
-
-    public static GraphicBuffer composeTaskBitmap(TaskView taskView, TaskViewTransform transform) {
-        float scale = transform.scale;
-        int fromWidth = (int) (transform.rect.width() * scale);
-        int fromHeight = (int) (transform.rect.height() * scale);
-        if (fromWidth == 0 || fromHeight == 0) {
-            Log.e(TAG, "Could not compose thumbnail for task: " + taskView.getTask() +
-                    " at transform: " + transform);
-
-            return drawViewIntoGraphicBuffer(1, 1, null, 1f, 0x00ffffff);
-        } else {
-            if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-                return drawViewIntoGraphicBuffer(fromWidth, fromHeight, null, 1f, 0xFFff0000);
-            } else {
-                return drawViewIntoGraphicBuffer(fromWidth, fromHeight, taskView, scale, 0);
-            }
-        }
-    }
-
-    private static GraphicBuffer composeHeaderBitmap(TaskView taskView,
-            TaskViewTransform transform) {
-        float scale = transform.scale;
-        int headerWidth = (int) (transform.rect.width());
-        int headerHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
-        if (headerWidth == 0 || headerHeight == 0) {
-            return null;
-        }
-
-        if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
-            return drawViewIntoGraphicBuffer(headerWidth, headerHeight, null, 1f, 0xFFff0000);
-        } else {
-            return drawViewIntoGraphicBuffer(headerWidth, headerHeight, taskView.mHeaderView,
-                    scale, 0);
-        }
-    }
-
-    public static GraphicBuffer drawViewIntoGraphicBuffer(int bufferWidth, int bufferHeight,
-            View view, float scale, int eraseColor) {
-        RenderNode node = RenderNode.create("RecentsTransition", null);
-        node.setLeftTopRightBottom(0, 0, bufferWidth, bufferHeight);
-        node.setClipToBounds(false);
-        DisplayListCanvas c = node.start(bufferWidth, bufferHeight);
-        c.scale(scale, scale);
-        if (eraseColor != 0) {
-            c.drawColor(eraseColor);
-        }
-        if (view != null) {
-            view.draw(c);
-        }
-        node.end(c);
-        Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, bufferWidth, bufferHeight);
-        return hwBitmap.createGraphicBufferHandle();
-    }
-
-    /**
-     * Composes a single animation spec for the given {@link TaskView}
-     */
-    private static AppTransitionAnimationSpec composeAnimationSpec(TaskStackView stackView,
-            TaskView taskView, TaskViewTransform transform, boolean addHeaderBitmap) {
-        GraphicBuffer b = null;
-        if (addHeaderBitmap) {
-            b = composeHeaderBitmap(taskView, transform);
-            if (b == null) {
-                return null;
-            }
-        }
-
-        Rect taskRect = new Rect();
-        transform.rect.round(taskRect);
-        // Disable in for low ram devices because each task does in Recents does not have fullscreen
-        // height (stackView height) and when transitioning to fullscreen app, the code below would
-        // force the task thumbnail to full stackView height immediately causing the transition
-        // jarring.
-        if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
-                stackView.getStack().getStackFrontMostTask()) {
-            taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
-        }
-        return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
-    }
-
-    public interface AnimationSpecComposer {
-        List<AppTransitionAnimationSpec> composeSpecs();
-    }
-
-    /**
-     * Class to be returned from {@link #composeAnimationSpec} that gives access to both the future
-     * and the anonymous class used for composing.
-     */
-    public class AppTransitionAnimationSpecsFuture {
-
-        private final AnimationSpecComposer composer;
-        private final IAppTransitionAnimationSpecsFuture future;
-
-        private AppTransitionAnimationSpecsFuture(AnimationSpecComposer composer,
-                IAppTransitionAnimationSpecsFuture future) {
-            this.composer = composer;
-            this.future = future;
-        }
-
-        public IAppTransitionAnimationSpecsFuture getFuture() {
-            return future;
-        }
-
-        /**
-         * Manually generates and caches the spec such that they are already available when the
-         * future needs.
-         */
-        public void precacheSpecs() {
-            synchronized (RecentsTransitionHelper.this) {
-                mAppTransitionAnimationSpecs = composer.composeSpecs();
-            }
-        }
-    }
-}
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index 5f12a04..1440fc1 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -16,8 +16,12 @@
 
 package com.android.systemui.recents.views;
 
+import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
+
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -28,8 +32,10 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.util.ArraySet;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
@@ -54,15 +60,22 @@
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
 import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
 import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
 import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
@@ -76,8 +89,10 @@
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.TaskStack;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
-import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.WindowManagerProxy;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -101,6 +116,7 @@
 
     private static final int BUSY_RECENTS_TASK_COUNT = 3;
 
+    private Handler mHandler;
     private TaskStackView mTaskStackView;
     private TextView mStackActionButton;
     private TextView mEmptyView;
@@ -126,7 +142,7 @@
         mMultiWindowBackgroundScrim.setAlpha(alpha);
     };
 
-    private RecentsTransitionHelper mTransitionHelper;
+    private RecentsTransitionComposer mTransitionHelper;
     @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
     private RecentsViewTouchHandler mTouchHandler;
     private final FlingAnimationUtils mFlingAnimationUtils;
@@ -148,7 +164,8 @@
         setWillNotDraw(false);
 
         SystemServicesProxy ssp = Recents.getSystemServices();
-        mTransitionHelper = new RecentsTransitionHelper(getContext());
+        mHandler = new Handler();
+        mTransitionHelper = new RecentsTransitionComposer(getContext());
         mDividerSize = ssp.getDockedDividerSize(context);
         mTouchHandler = new RecentsViewTouchHandler(this);
         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
@@ -279,7 +296,7 @@
      * @return True if it changed.
      */
     private boolean updateBusyness() {
-        final int taskCount = mTaskStackView.getStack().getStackTaskCount();
+        final int taskCount = mTaskStackView.getStack().getTaskCount();
         final float busyness = Math.min(taskCount, BUSY_RECENTS_TASK_COUNT)
                 / (float) BUSY_RECENTS_TASK_COUNT;
         if (mBusynessFactor == busyness) {
@@ -518,9 +535,8 @@
     /**** EventBus Events ****/
 
     public final void onBusEvent(LaunchTaskEvent event) {
-        mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
-                event.taskView, event.screenPinningRequested, event.targetWindowingMode,
-                event.targetActivityType);
+        launchTaskFromRecents(getStack(), event.task, mTaskStackView, event.taskView,
+                event.screenPinningRequested, event.targetWindowingMode, event.targetActivityType);
         if (Recents.getConfiguration().isLowRamDevice) {
             EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
         }
@@ -595,29 +611,23 @@
             // Dock the task and launch it
             SystemServicesProxy ssp = Recents.getSystemServices();
             if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
-                final OnAnimationStartedListener startedListener =
-                        new OnAnimationStartedListener() {
-                    @Override
-                    public void onAnimationStarted() {
-                        EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
-                        // Remove the task and don't bother relaying out, as all the tasks will be
-                        // relaid out when the stack changes on the multiwindow change event
-                        getStack().removeTask(event.task, null, true /* fromDockGesture */);
-                    }
+                final Runnable animStartedListener = () -> {
+                    EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
+                    // Remove the task and don't bother relaying out, as all the tasks will be
+                    // relaid out when the stack changes on the multiwindow change event
+                    getStack().removeTask(event.task, null, true /* fromDockGesture */);
                 };
 
                 final Rect taskRect = getTaskRect(event.taskView);
-                AppTransitionAnimationSpecsFuture future =
-                        mTransitionHelper.getAppTransitionFuture(
-                                new AnimationSpecComposer() {
-                                    @Override
-                                    public List<AppTransitionAnimationSpec> composeSpecs() {
-                                        return mTransitionHelper.composeDockAnimationSpec(
-                                                event.taskView, taskRect);
-                                    }
-                                });
+                AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
+                        getHandler()) {
+                    @Override
+                    public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+                        return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
+                    }
+                };
                 ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
-                        mTransitionHelper.wrapStartedListener(startedListener),
+                        RecentsTransition.wrapStartedListener(getHandler(), animStartedListener),
                         true /* scaleUp */);
 
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
@@ -881,7 +891,8 @@
      * @return the bounds of the stack action button.
      */
     Rect getStackActionButtonBoundsFromStackLayout() {
-        Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
+        Rect actionButtonRect = new Rect(
+                mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
         int left, top;
         if (Recents.getConfiguration().isLowRamDevice) {
             Rect windowRect = Recents.getSystemServices().getWindowRect();
@@ -906,6 +917,141 @@
         return mStackActionButton;
     }
 
+    /**
+     * Launches the specified {@link Task}.
+     */
+    public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
+            final TaskStackView stackView, final TaskView taskView,
+            final boolean screenPinningRequested, final int windowingMode, final int activityType) {
+
+        final Runnable animStartedListener;
+        final AppTransitionAnimationSpecsFuture transitionFuture;
+        if (taskView != null) {
+
+            // Fetch window rect here already in order not to be blocked on lock contention in WM
+            // when the future calls it.
+            final Rect windowRect = Recents.getSystemServices().getWindowRect();
+            transitionFuture = new AppTransitionAnimationSpecsFuture(stackView.getHandler()) {
+                @Override
+                public List<AppTransitionAnimationSpecCompat> composeSpecs() {
+                    return mTransitionHelper.composeAnimationSpecs(task, stackView, windowingMode,
+                            activityType, windowRect);
+                }
+            };
+            animStartedListener = new Runnable() {
+                private boolean mHandled;
+
+                @Override
+                public void run() {
+                    if (mHandled) {
+                        return;
+                    }
+                    mHandled = true;
+
+                    // If we are launching into another task, cancel the previous task's
+                    // window transition
+                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                    stackView.cancelAllTaskViewAnimations();
+
+                    if (screenPinningRequested) {
+                        // Request screen pinning after the animation runs
+                        mHandler.postDelayed(() -> {
+                            EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext,
+                                    task.key.id));
+                        }, 350);
+                    }
+
+                    if (!Recents.getConfiguration().isLowRamDevice) {
+                        // Reset the state where we are waiting for the transition to start
+                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+                    }
+                }
+            };
+        } else {
+            // This is only the case if the task is not on screen (scrolled offscreen for example)
+            transitionFuture = null;
+            animStartedListener = new Runnable() {
+                private boolean mHandled;
+
+                @Override
+                public void run() {
+                    if (mHandled) {
+                        return;
+                    }
+                    mHandled = true;
+
+                    // If we are launching into another task, cancel the previous task's
+                    // window transition
+                    EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+                    EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
+                    stackView.cancelAllTaskViewAnimations();
+
+                    if (!Recents.getConfiguration().isLowRamDevice) {
+                        // Reset the state where we are waiting for the transition to start
+                        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(false));
+                    }
+                }
+            };
+        }
+
+        EventBus.getDefault().send(new SetWaitingForTransitionStartEvent(true));
+        final ActivityOptions opts = RecentsTransition.createAspectScaleAnimation(mContext,
+                mHandler, true /* scaleUp */, transitionFuture != null ? transitionFuture : null,
+                animStartedListener);
+        if (taskView == null) {
+            // If there is no task view, then we do not need to worry about animating out occluding
+            // task views, and we can launch immediately
+            startTaskActivity(stack, task, taskView, opts, transitionFuture,
+                    windowingMode, activityType);
+        } else {
+            LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
+                    screenPinningRequested);
+            EventBus.getDefault().send(launchStartedEvent);
+            startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
+                    activityType);
+        }
+        ActivityManagerWrapper.getInstance().closeSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
+    }
+
+    /**
+     * Starts the activity for the launch task.
+     *
+     * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
+     *                 we are toggling recents and the launch-to task is now offscreen.
+     */
+    private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
+            ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
+            int windowingMode, int activityType) {
+        ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(task.key, opts,
+                windowingMode, activityType, succeeded -> {
+            if (succeeded) {
+                // Keep track of the index of the task launch
+                int taskIndexFromFront = 0;
+                int taskIndex = stack.indexOfTask(task);
+                if (taskIndex > -1) {
+                    taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
+                }
+                EventBus.getDefault().send(new LaunchTaskSucceededEvent(
+                        taskIndexFromFront));
+            } else {
+                Log.e(TAG, mContext.getString(R.string.recents_launch_error_message,
+                        task.title));
+
+                // Dismiss the task if we fail to launch it
+                if (taskView != null) {
+                    taskView.dismissTask();
+                }
+
+                // Keep track of failed launches
+                EventBus.getDefault().send(new LaunchTaskFailedEvent());
+            }
+        }, getHandler());
+        if (transitionFuture != null) {
+            mHandler.post(transitionFuture::composeSpecsSynchronous);
+        }
+    }
+
     @Override
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         super.requestDisallowInterceptTouchEvent(disallowIntercept);
diff --git a/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 0cfdbde..5c69ae3 100644
--- a/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -40,7 +40,6 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.TaskStack;
 
 import java.util.ArrayList;
 
@@ -106,7 +105,7 @@
     /** Handles touch events once we have intercepted them */
     public boolean onTouchEvent(MotionEvent ev) {
         handleTouchEvent(ev);
-        if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getStackTaskCount() == 0) {
+        if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getTaskCount() == 0) {
             EventBus.getDefault().send(new HideRecentsEvent(false, true));
         }
         return true;
diff --git a/com/android/systemui/recents/views/SystemBarScrimViews.java b/com/android/systemui/recents/views/SystemBarScrimViews.java
index 7827c59..170e39d 100644
--- a/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -154,7 +154,7 @@
 
     public final void onBusEvent(MultiWindowStateChangedEvent event) {
         mHasDockedTasks = event.inMultiWindow;
-        animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0);
+        animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0);
     }
 
     public final void onBusEvent(final DragEndEvent event) {
@@ -166,7 +166,7 @@
 
     public final void onBusEvent(final DragEndCancelledEvent event) {
         // Restore the scrims to the normal state
-        animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0);
+        animateScrimToCurrentNavBarState(event.stack.getTaskCount() > 0);
     }
 
     /**
diff --git a/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 26db26f..67d0978 100644
--- a/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -449,7 +449,7 @@
 
         // Get the current set of task transforms
         int taskViewCount = mStackView.getTaskViews().size();
-        ArrayList<Task> stackTasks = stack.getStackTasks();
+        ArrayList<Task> stackTasks = stack.getTasks();
         mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
 
         // Pick up the newly visible views after the scroll
@@ -541,7 +541,7 @@
         TaskStackViewScroller stackScroller = mStackView.getScroller();
 
         // Get the current set of task transforms
-        ArrayList<Task> stackTasks = newStack.getStackTasks();
+        ArrayList<Task> stackTasks = newStack.getTasks();
         mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
 
         // Update the stack
@@ -563,7 +563,7 @@
                 false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
 
         // Hide the front most task view until the scroll is complete
-        Task frontMostTask = newStack.getStackFrontMostTask();
+        Task frontMostTask = newStack.getFrontMostTask();
         final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
         final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
                 stackTasks.indexOf(frontMostTask));
diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index acb058c..600da04 100644
--- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -438,7 +438,7 @@
         mTaskIndexMap.clear();
 
         // Return early if we have no tasks
-        ArrayList<Task> tasks = stack.getStackTasks();
+        ArrayList<Task> tasks = stack.getTasks();
         if (tasks.isEmpty()) {
             mFrontMostTaskP = 0;
             mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
@@ -468,7 +468,7 @@
         // Calculate the min/max/initial scroll
         Task launchTask = stack.getLaunchTarget();
         int launchTaskIndex = launchTask != null
-                ? stack.indexOfStackTask(launchTask)
+                ? stack.indexOfTask(launchTask)
                 : mNumStackTasks - 1;
         if (getInitialFocusState() == STATE_FOCUSED) {
             int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
@@ -557,7 +557,7 @@
                 }
 
                 mUnfocusedRange.offset(0f);
-                List<Task> tasks = stack.getStackTasks();
+                List<Task> tasks = stack.getTasks();
                 int taskCount = tasks.size();
                 for (int i = taskCount - 1; i >= 0; i--) {
                     int indexFromFront = taskCount - i - 1;
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 428113a..1197501 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -535,7 +535,7 @@
      */
     void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) {
         // Get all the task transforms
-        ArrayList<Task> tasks = mStack.getStackTasks();
+        ArrayList<Task> tasks = mStack.getTasks();
         int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks,
                 mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks,
                 ignoreTaskOverrides);
@@ -557,7 +557,7 @@
             // It is possible for the set of lingering TaskViews to differ from the stack if the
             // stack was updated before the relayout.  If the task view is no longer in the stack,
             // then just return it back to the view pool.
-            int taskIndex = mStack.indexOfStackTask(task);
+            int taskIndex = mStack.indexOfTask(task);
             TaskViewTransform transform = null;
             if (taskIndex != -1) {
                 transform = mCurrentTaskTransforms.get(taskIndex);
@@ -601,7 +601,7 @@
                 }
             } else {
                 // Reattach it in the right z order
-                final int taskIndex = mStack.indexOfStackTask(task);
+                final int taskIndex = mStack.indexOfTask(task);
                 final int insertIndex = findTaskViewInsertIndex(task, taskIndex);
                 if (insertIndex != getTaskViews().indexOf(tv)){
                     detachViewFromParent(tv);
@@ -663,7 +663,7 @@
                 continue;
             }
 
-            int taskIndex = mStack.indexOfStackTask(task);
+            int taskIndex = mStack.indexOfTask(task);
             TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
             if (animationOverrides != null && animationOverrides.containsKey(task)) {
                 animation = animationOverrides.get(task);
@@ -866,7 +866,7 @@
         int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
                 Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1;
         final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
-                mStack.getStackTasks().get(newFocusedTaskIndex) : null;
+                mStack.getTasks().get(newFocusedTaskIndex) : null;
 
         // Reset the last focused task state if changed
         if (mFocusedTask != null) {
@@ -953,10 +953,10 @@
     public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
                                        boolean cancelWindowAnimations, int timerIndicatorDuration) {
         Task focusedTask = getFocusedTask();
-        int newIndex = mStack.indexOfStackTask(focusedTask);
+        int newIndex = mStack.indexOfTask(focusedTask);
         if (focusedTask != null) {
             if (stackTasksOnly) {
-                List<Task> tasks =  mStack.getStackTasks();
+                List<Task> tasks =  mStack.getTasks();
                 // Try the next task if it is a stack task
                 int tmpNewIndex = newIndex + (forward ? -1 : 1);
                 if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
@@ -971,7 +971,7 @@
         } else {
             // We don't have a focused task
             float stackScroll = mStackScroller.getStackScroll();
-            ArrayList<Task> tasks = mStack.getStackTasks();
+            ArrayList<Task> tasks = mStack.getTasks();
             int taskCount = tasks.size();
             if (useGridLayout()) {
                 // For the grid layout, we directly set focus to the most recently used task
@@ -1060,8 +1060,8 @@
         if (taskViewCount > 0) {
             TaskView backMostTask = taskViews.get(0);
             TaskView frontMostTask = taskViews.get(taskViewCount - 1);
-            event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask()));
-            event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask()));
+            event.setFromIndex(mStack.indexOfTask(backMostTask.getTask()));
+            event.setToIndex(mStack.indexOfTask(frontMostTask.getTask()));
             event.setContentDescription(frontMostTask.getTask().title);
         }
         event.setItemCount(mStack.getTaskCount());
@@ -1080,7 +1080,7 @@
             // Find the accessibility focused task
             Task focusedTask = getAccessibilityFocusedTask();
             info.setScrollable(true);
-            int focusedTaskIndex = mStack.indexOfStackTask(focusedTask);
+            int focusedTaskIndex = mStack.indexOfTask(focusedTask);
             if (focusedTaskIndex > 0 || !mStackActionButtonVisible) {
                 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
             }
@@ -1101,7 +1101,7 @@
             return true;
         }
         Task focusedTask = getAccessibilityFocusedTask();
-        int taskIndex = mStack.indexOfStackTask(focusedTask);
+        int taskIndex = mStack.indexOfTask(focusedTask);
         if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
             switch (action) {
                 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
@@ -1157,7 +1157,7 @@
      * updateLayoutForStack() is called first.
      */
     public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
-        return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks());
+        return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
     }
 
     /**
@@ -1328,10 +1328,9 @@
         // We set the initial focused task view iff the following conditions are satisfied:
         // 1. Recents is showing task views in stack layout.
         // 2. Recents is launched with ALT + TAB.
-        boolean setFocusOnFirstLayout = !useGridLayout() ||
-            Recents.getConfiguration().getLaunchState().launchedWithAltTab;
+        boolean setFocusOnFirstLayout = !useGridLayout() || launchState.launchedWithAltTab;
         if (setFocusOnFirstLayout) {
-            int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount(),
+            int focusedTaskIndex = getInitialFocusTaskIndex(launchState, mStack.getTaskCount(),
                 useGridLayout());
             if (focusedTaskIndex != -1) {
                 setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
@@ -1502,7 +1501,7 @@
     @Override
     public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) {
         // Find the index where this task should be placed in the stack
-        int taskIndex = mStack.indexOfStackTask(task);
+        int taskIndex = mStack.indexOfTask(task);
         int insertIndex = findTaskViewInsertIndex(task, taskIndex);
 
         // Add/attach the view to the hierarchy
@@ -1545,7 +1544,7 @@
         }
 
         // Restore the action button visibility if it is the front most task view
-        if (mScreenPinningEnabled && tv.getTask() == mStack.getStackFrontMostTask()) {
+        if (mScreenPinningEnabled && tv.getTask() == mStack.getFrontMostTask()) {
             tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
         }
     }
@@ -1669,7 +1668,7 @@
                 event.packageName, event.userId);
 
         // For other tasks, just remove them directly if they no longer exist
-        ArrayList<Task> tasks = mStack.getStackTasks();
+        ArrayList<Task> tasks = mStack.getTasks();
         for (int i = tasks.size() - 1; i >= 0; i--) {
             final Task t = tasks.get(i);
             if (removedComponents.contains(t.key.getComponent())) {
@@ -1692,7 +1691,7 @@
 
     public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) {
         if (mStack.getTaskCount() > 0) {
-            Task mostRecentTask = mStack.getStackFrontMostTask();
+            Task mostRecentTask = mStack.getFrontMostTask();
             launchTask(mostRecentTask);
         }
     }
@@ -1786,7 +1785,7 @@
 
     public final void onBusEvent(final DismissAllTaskViewsEvent event) {
         // Keep track of the tasks which will have their data removed
-        ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks());
+        ArrayList<Task> tasks = new ArrayList<>(mStack.getTasks());
         mAnimationHelper.startDeleteAllTasksAnimation(
                 getTaskViews(), useGridLayout(), event.getAnimationTrigger());
         event.addPostAnimationCallback(new Runnable() {
@@ -1854,7 +1853,7 @@
     public final void onBusEvent(NavigateTaskViewEvent event) {
         if (useGridLayout()) {
             final int taskCount = mStack.getTaskCount();
-            final int currentIndex = mStack.indexOfStackTask(getFocusedTask());
+            final int currentIndex = mStack.indexOfTask(getFocusedTask());
             final int nextIndex = mLayoutAlgorithm.mTaskGridLayoutAlgorithm.navigateFocus(taskCount,
                     currentIndex, event.direction);
             setFocusedTask(nextIndex, false, true);
@@ -2000,7 +1999,7 @@
                 if (mFocusedTask != null) {
                     RecentsConfiguration config = Recents.getConfiguration();
                     RecentsActivityLaunchState launchState = config.getLaunchState();
-                    setFocusedTask(mStack.indexOfStackTask(mFocusedTask),
+                    setFocusedTask(mStack.indexOfTask(mFocusedTask),
                             false /* scrollToTask */, launchState.launchedWithAltTab);
                     TaskView focusedTaskView = getChildViewForTask(mFocusedTask);
                     if (mTouchExplorationEnabled && focusedTaskView != null) {
@@ -2131,7 +2130,7 @@
                 Task tvTask = taskViews.get(i).getTask();
                 if (tvTask == task) {
                     foundTaskView = true;
-                } else if (taskIndex < mStack.indexOfStackTask(tvTask)) {
+                } else if (taskIndex < mStack.indexOfTask(tvTask)) {
                     if (foundTaskView) {
                         return i - 1;
                     } else {
@@ -2201,6 +2200,25 @@
     }
 
     /**
+     * Returns the task to focus given the current launch state.
+     */
+    private int getInitialFocusTaskIndex(RecentsActivityLaunchState launchState, int numTasks,
+            boolean useGridLayout) {
+        if (launchState.launchedFromApp) {
+            if (useGridLayout) {
+                // If coming from another app to the grid layout, focus the front most task
+                return numTasks - 1;
+            }
+
+            // If coming from another app, focus the next task
+            return Math.max(0, numTasks - 2);
+        } else {
+            // If coming from home, focus the front most task
+            return numTasks - 1;
+        }
+    }
+
+    /**
      * Updates {@param transforms} to be the same size as {@param tasks}.
      */
     private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
diff --git a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index b9ca248..bfaa8cd 100644
--- a/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -446,7 +446,7 @@
         TaskView tv = (TaskView) v;
         Task task = tv.getTask();
         return !mSwipeHelperAnimations.containsKey(v) &&
-                (mSv.getStack().indexOfStackTask(task) != -1);
+                (mSv.getStack().indexOfTask(task) != -1);
     }
 
     /**
@@ -476,7 +476,7 @@
         mSv.addIgnoreTask(tv.getTask());
 
         // Determine if we are animating the other tasks while dismissing this task
-        mCurrentTasks = new ArrayList<Task>(mSv.getStack().getStackTasks());
+        mCurrentTasks = new ArrayList<Task>(mSv.getStack().getTasks());
         MutableBoolean isFrontMostTask = new MutableBoolean(false);
         Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask);
         TaskStackLayoutAlgorithm layoutAlgorithm = mSv.getStackAlgorithm();
@@ -673,7 +673,7 @@
 
     /** Returns the view at the specified coordinates */
     private TaskView findViewAtPoint(int x, int y) {
-        List<Task> tasks = mSv.getStack().getStackTasks();
+        List<Task> tasks = mSv.getStack().getTasks();
         int taskCount = tasks.size();
         for (int i = taskCount - 1; i >= 0; i--) {
             TaskView tv = mSv.getChildViewForTask(tasks.get(i));
diff --git a/com/android/systemui/recents/views/TaskView.java b/com/android/systemui/recents/views/TaskView.java
index b440847..f0278a6 100644
--- a/com/android/systemui/recents/views/TaskView.java
+++ b/com/android/systemui/recents/views/TaskView.java
@@ -55,6 +55,7 @@
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -683,7 +684,7 @@
         }
         SystemServicesProxy ssp = Recents.getSystemServices();
         boolean inBounds = false;
-        Rect clipBounds = new Rect(mViewBounds.mClipBounds);
+        Rect clipBounds = new Rect(mViewBounds.getClipBounds());
         if (!clipBounds.isEmpty()) {
             // If we are clipping the view to the bounds, manually do the hit test.
             clipBounds.scale(getScaleX());
diff --git a/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
index a029478..3bdad31 100644
--- a/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
+++ b/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.views.grid;
 
 import android.view.View;
-import com.android.systemui.recents.views.AnimateableViewBounds;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
 
 /* An outline provider for grid-based task views. */
 class AnimateableGridViewBounds extends AnimateableViewBounds {
diff --git a/com/android/systemui/recents/views/grid/GridTaskView.java b/com/android/systemui/recents/views/grid/GridTaskView.java
index 8b4700c..0d51154 100644
--- a/com/android/systemui/recents/views/grid/GridTaskView.java
+++ b/com/android/systemui/recents/views/grid/GridTaskView.java
@@ -19,7 +19,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import com.android.systemui.R;
-import com.android.systemui.recents.views.AnimateableViewBounds;
+import com.android.systemui.shared.recents.view.AnimateableViewBounds;
 import com.android.systemui.recents.views.TaskView;
 
 public class GridTaskView extends TaskView {
diff --git a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
index 95f1d58..fe6bafb 100644
--- a/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
+++ b/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
@@ -125,7 +125,7 @@
             // We're returning from touch mode, set the focus to the previously focused task.
             final TaskStack stack = mSv.getStack();
             final int taskCount = stack.getTaskCount();
-            final int k = stack.indexOfStackTask(mSv.getFocusedTask());
+            final int k = stack.indexOfTask(mSv.getFocusedTask());
             final int taskIndexToFocus = k == -1 ? (taskCount - 1) : (k % taskCount);
             mSv.setFocusedTask(taskIndexToFocus, false, true);
         }
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
index 806a073..8e2a25c 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -70,21 +70,6 @@
     }
 
     /**
-     * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
-     * to most-recent order.
-     *
-     * Note: Do not lock, callers should synchronize on the loader before making this call.
-     */
-    void preloadRawTasks() {
-        int currentUserId = ActivityManagerWrapper.getInstance().getCurrentUserId();
-        mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
-                ActivityManager.getMaxRecentTasksStatic(), currentUserId);
-
-        // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
-        Collections.reverse(mRawTasks);
-    }
-
-    /**
      * Preloads the list of recent tasks from the system. After this call, the TaskStack will
      * have a list of all the recent tasks with their metadata, not including icons or
      * thumbnails which were not cached and have to be loaded.
@@ -95,11 +80,15 @@
      * Note: Do not lock, since this can be calling back to the loader, which separately also drives
      * this call (callers should synchronize on the loader before making this call).
      */
-    void preloadPlan(RecentsTaskLoader loader, int runningTaskId) {
+    public void preloadPlan(RecentsTaskLoader loader, int runningTaskId, int currentUserId) {
         Resources res = mContext.getResources();
         ArrayList<Task> allTasks = new ArrayList<>();
         if (mRawTasks == null) {
-            preloadRawTasks();
+            mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
+                    ActivityManager.getMaxRecentTasksStatic(), currentUserId);
+
+            // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
+            Collections.reverse(mRawTasks);
         }
 
         int taskCount = mRawTasks.size();
@@ -160,11 +149,11 @@
      * Note: Do not lock, since this can be calling back to the loader, which separately also drives
      * this call (callers should synchronize on the loader before making this call).
      */
-    void executePlan(Options opts, RecentsTaskLoader loader) {
+    public void executePlan(Options opts, RecentsTaskLoader loader) {
         Resources res = mContext.getResources();
 
         // Iterate through each of the tasks and load them according to the load conditions.
-        ArrayList<Task> tasks = mStack.getStackTasks();
+        ArrayList<Task> tasks = mStack.getTasks();
         int taskCount = tasks.size();
         for (int i = 0; i < taskCount; i++) {
             Task task = tasks.get(i);
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
index de4c72c..9a991cf 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
@@ -147,9 +147,15 @@
 
     /** Preloads recents tasks using the specified plan to store the output. */
     public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
+        preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId());
+    }
+
+    /** Preloads recents tasks using the specified plan to store the output. */
+    public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
+            int currentUserId) {
         try {
             Trace.beginSection("preloadPlan");
-            plan.preloadPlan(this, runningTaskId);
+            plan.preloadPlan(this, runningTaskId, currentUserId);
         } finally {
             Trace.endSection();
         }
diff --git a/com/android/systemui/shared/recents/model/TaskFilter.java b/com/android/systemui/shared/recents/model/TaskFilter.java
index 9a1ff54..5f3dcd1 100644
--- a/com/android/systemui/shared/recents/model/TaskFilter.java
+++ b/com/android/systemui/shared/recents/model/TaskFilter.java
@@ -21,7 +21,7 @@
 /**
  * An interface for a task filter to query whether a particular task should show in a stack.
  */
-interface TaskFilter {
+public interface TaskFilter {
     /** Returns whether the filter accepts the specified task */
     boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
 }
diff --git a/com/android/systemui/shared/recents/model/TaskStack.java b/com/android/systemui/shared/recents/model/TaskStack.java
index 693379d..a369397 100644
--- a/com/android/systemui/shared/recents/model/TaskStack.java
+++ b/com/android/systemui/shared/recents/model/TaskStack.java
@@ -19,7 +19,6 @@
 import android.content.ComponentName;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.SparseArray;
 
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.utilities.AnimationProps;
@@ -92,7 +91,7 @@
             boolean dismissRecentsIfAllRemoved) {
         if (mStackTaskList.contains(t)) {
             mStackTaskList.remove(t);
-            Task newFrontMostTask = getStackFrontMostTask();
+            Task newFrontMostTask = getFrontMostTask();
             if (mCb != null) {
                 // Notify that a task has been removed
                 mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
@@ -183,7 +182,7 @@
 
         // Only callback for the removed tasks after the stack has updated
         int removedTaskCount = removedTasks.size();
-        Task newFrontMostTask = getStackFrontMostTask();
+        Task newFrontMostTask = getFrontMostTask();
         for (int i = 0; i < removedTaskCount; i++) {
             mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
                     AnimationProps.IMMEDIATE, false /* fromDockGesture */,
@@ -205,7 +204,7 @@
     /**
      * Gets the front-most task in the stack.
      */
-    public Task getStackFrontMostTask() {
+    public Task getFrontMostTask() {
         ArrayList<Task> stackTasks = mStackTaskList.getTasks();
         if (stackTasks.isEmpty()) {
             return null;
@@ -228,7 +227,7 @@
     /**
      * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
      */
-    public ArrayList<Task> getStackTasks() {
+    public ArrayList<Task> getTasks() {
         return mStackTaskList.getTasks();
     }
 
@@ -242,16 +241,9 @@
     }
 
     /**
-     * Returns the number of stacktasks.
-     */
-    public int getTaskCount() {
-        return mStackTaskList.size();
-    }
-
-    /**
      * Returns the number of stack tasks.
      */
-    public int getStackTaskCount() {
+    public int getTaskCount() {
         return mStackTaskList.size();
     }
 
@@ -298,7 +290,7 @@
         if (nextLaunchTarget != null) {
             return nextLaunchTarget;
         }
-        return getStackTasks().get(getTaskCount() - 1);
+        return getTasks().get(getTaskCount() - 1);
     }
 
     private Task getNextLaunchTargetRaw() {
@@ -306,15 +298,15 @@
         if (taskCount == 0) {
             return null;
         }
-        int launchTaskIndex = indexOfStackTask(getLaunchTarget());
+        int launchTaskIndex = indexOfTask(getLaunchTarget());
         if (launchTaskIndex != -1 && launchTaskIndex > 0) {
-            return getStackTasks().get(launchTaskIndex - 1);
+            return getTasks().get(launchTaskIndex - 1);
         }
         return null;
     }
 
     /** Returns the index of this task in this current task stack */
-    public int indexOfStackTask(Task t) {
+    public int indexOfTask(Task t) {
         return mStackTaskList.indexOf(t);
     }
 
diff --git a/com/android/systemui/recents/views/AnimateableViewBounds.java b/com/android/systemui/shared/recents/view/AnimateableViewBounds.java
similarity index 86%
rename from com/android/systemui/recents/views/AnimateableViewBounds.java
rename to com/android/systemui/shared/recents/view/AnimateableViewBounds.java
index b598ec6..45728c4 100644
--- a/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/com/android/systemui/shared/recents/view/AnimateableViewBounds.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.views;
+package com.android.systemui.shared.recents.view;
 
 import android.graphics.Outline;
 import android.graphics.Rect;
@@ -24,22 +24,19 @@
 
 import com.android.systemui.shared.recents.utilities.Utilities;
 
-/* An outline provider that has a clip and outline that can be animated. */
+/**
+ * An outline provider that has a clip and outline that can be animated.
+ */
 public class AnimateableViewBounds extends ViewOutlineProvider {
 
     private static final float MIN_ALPHA = 0.1f;
     private static final float MAX_ALPHA = 0.8f;
 
     protected View mSourceView;
-    @ViewDebug.ExportedProperty(category="recents")
     protected Rect mClipRect = new Rect();
-    @ViewDebug.ExportedProperty(category="recents")
     protected Rect mClipBounds = new Rect();
-    @ViewDebug.ExportedProperty(category="recents")
     protected Rect mLastClipBounds = new Rect();
-    @ViewDebug.ExportedProperty(category="recents")
     protected int mCornerRadius;
-    @ViewDebug.ExportedProperty(category="recents")
     protected float mAlpha = 1f;
 
     public AnimateableViewBounds(View source, int cornerRadius) {
@@ -73,7 +70,7 @@
     /**
      * Sets the view outline alpha.
      */
-    void setAlpha(float alpha) {
+    public void setAlpha(float alpha) {
         if (Float.compare(alpha, mAlpha) != 0) {
             mAlpha = alpha;
             // TODO, If both clip and alpha change in the same frame, only invalidate once
@@ -88,28 +85,43 @@
         return mAlpha;
     }
 
-    /** Sets the top clip. */
+    /**
+     * Sets the top clip.
+     */
     public void setClipTop(int top) {
         mClipRect.top = top;
         updateClipBounds();
     }
 
-    /** Returns the top clip. */
+    /**
+     * @return the top clip.
+     */
     public int getClipTop() {
         return mClipRect.top;
     }
 
-    /** Sets the bottom clip. */
+    /**
+     * Sets the bottom clip.
+     */
     public void setClipBottom(int bottom) {
         mClipRect.bottom = bottom;
         updateClipBounds();
     }
 
-    /** Returns the bottom clip. */
+    /**
+     * @return the bottom clip.
+     */
     public int getClipBottom() {
         return mClipRect.bottom;
     }
 
+    /**
+     * @return the clip bounds.
+     */
+    public Rect getClipBounds() {
+        return mClipBounds;
+    }
+
     protected void updateClipBounds() {
         mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top),
                 mSourceView.getWidth() - Math.max(0, mClipRect.right),
diff --git a/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java b/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java
new file mode 100644
index 0000000..ebdc884
--- /dev/null
+++ b/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecCompat.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.recents.view;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.view.AppTransitionAnimationSpec;
+
+/**
+ * Wraps the internal app transition animation spec.
+ */
+public class AppTransitionAnimationSpecCompat {
+
+    private int mTaskId;
+    private Bitmap mBuffer;
+    private Rect mRect;
+
+    public AppTransitionAnimationSpecCompat(int taskId, Bitmap buffer, Rect rect) {
+        mTaskId = taskId;
+        mBuffer = buffer;
+        mRect = rect;
+    }
+
+    public AppTransitionAnimationSpec toAppTransitionAnimationSpec() {
+        return new AppTransitionAnimationSpec(mTaskId,
+                mBuffer != null ? mBuffer.createGraphicBufferHandle() : null, mRect);
+    }
+}
diff --git a/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java b/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java
new file mode 100644
index 0000000..85d362a
--- /dev/null
+++ b/com/android/systemui/shared/recents/view/AppTransitionAnimationSpecsFuture.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.recents.view;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+/**
+ * To be implemented by a particular animation to asynchronously provide the animation specs for a
+ * particular transition.
+ */
+public abstract class AppTransitionAnimationSpecsFuture {
+
+    private final Handler mHandler;
+    private FutureTask<List<AppTransitionAnimationSpecCompat>> mComposeTask = new FutureTask<>(
+            new Callable<List<AppTransitionAnimationSpecCompat>>() {
+                @Override
+                public List<AppTransitionAnimationSpecCompat> call() throws Exception {
+                    return composeSpecs();
+                }
+            });
+
+    private final IAppTransitionAnimationSpecsFuture mFuture =
+            new IAppTransitionAnimationSpecsFuture.Stub() {
+        @Override
+        public AppTransitionAnimationSpec[] get() throws RemoteException {
+            try {
+                if (!mComposeTask.isDone()) {
+                    mHandler.post(mComposeTask);
+                }
+                List<AppTransitionAnimationSpecCompat> specs = mComposeTask.get();
+                if (specs == null) {
+                    return null;
+                }
+
+                AppTransitionAnimationSpec[] arr = new AppTransitionAnimationSpec[specs.size()];
+                for (int i = 0; i < specs.size(); i++) {
+                    arr[i] = specs.get(i).toAppTransitionAnimationSpec();
+                }
+                return arr;
+            } catch (Exception e) {
+                return null;
+            }
+        }
+    };
+
+    public AppTransitionAnimationSpecsFuture(Handler handler) {
+        mHandler = handler;
+    }
+
+    /**
+     * Returns the future to handle the call from window manager.
+     */
+    public final IAppTransitionAnimationSpecsFuture getFuture() {
+        return mFuture;
+    }
+
+    /**
+     * Called ahead of the future callback to compose the specs to be returned in the future.
+     */
+    public final void composeSpecsSynchronous() {
+        if (Looper.myLooper() != mHandler.getLooper()) {
+            throw new RuntimeException("composeSpecsSynchronous() called from wrong looper");
+        }
+        mComposeTask.run();
+    }
+
+    public abstract List<AppTransitionAnimationSpecCompat> composeSpecs();
+}
diff --git a/com/android/systemui/shared/recents/view/RecentsTransition.java b/com/android/systemui/shared/recents/view/RecentsTransition.java
new file mode 100644
index 0000000..ab89043
--- /dev/null
+++ b/com/android/systemui/shared/recents/view/RecentsTransition.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shared.recents.view;
+
+import android.app.ActivityOptions;
+import android.app.ActivityOptions.OnAnimationStartedListener;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.GraphicBuffer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.ThreadedRenderer;
+import android.view.View;
+
+import java.util.function.Consumer;
+
+/**
+ * A helper class to create transitions to/from an App to Recents.
+ */
+public class RecentsTransition {
+
+    /**
+     * Creates a new transition aspect scaled transition activity options.
+     */
+    public static ActivityOptions createAspectScaleAnimation(Context context, Handler handler,
+            boolean scaleUp, AppTransitionAnimationSpecsFuture animationSpecsFuture,
+            final Runnable animationStartCallback) {
+        final OnAnimationStartedListener animStartedListener = new OnAnimationStartedListener() {
+            private boolean mHandled;
+
+            @Override
+            public void onAnimationStarted() {
+                // OnAnimationStartedListener can be called numerous times, so debounce here to
+                // prevent multiple callbacks
+                if (mHandled) {
+                    return;
+                }
+                mHandled = true;
+
+                if (animationStartCallback != null) {
+                    animationStartCallback.run();
+                }
+            }
+        };
+        final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(
+                context, handler,
+                animationSpecsFuture != null ? animationSpecsFuture.getFuture() : null,
+                animStartedListener, scaleUp);
+        return opts;
+    }
+
+    /**
+     * Wraps a animation-start callback in a binder that can be called from window manager.
+     */
+    public static IRemoteCallback wrapStartedListener(final Handler handler,
+            final Runnable animationStartCallback) {
+        if (animationStartCallback == null) {
+            return null;
+        }
+        return new IRemoteCallback.Stub() {
+            @Override
+            public void sendResult(Bundle data) throws RemoteException {
+                handler.post(animationStartCallback);
+            }
+        };
+    }
+
+    /**
+     * @return a {@link GraphicBuffer} with the {@param view} drawn into it. Result can be null if
+     *         we were unable to allocate a hardware bitmap.
+     */
+    public static Bitmap drawViewIntoHardwareBitmap(int width, int height, final View view,
+            final float scale, final int eraseColor) {
+        return createHardwareBitmap(width, height, new Consumer<Canvas>() {
+            @Override
+            public void accept(Canvas c) {
+                c.scale(scale, scale);
+                if (eraseColor != 0) {
+                    c.drawColor(eraseColor);
+                }
+                if (view != null) {
+                    view.draw(c);
+                }
+            }
+        });
+    }
+
+    /**
+     * @return a hardware {@link Bitmap} after being drawn with the {@param consumer}. Result can be
+     *         null if we were unable to allocate a hardware bitmap.
+     */
+    public static Bitmap createHardwareBitmap(int width, int height, Consumer<Canvas> consumer) {
+        RenderNode node = RenderNode.create("RecentsTransition", null);
+        node.setLeftTopRightBottom(0, 0, width, height);
+        node.setClipToBounds(false);
+        DisplayListCanvas c = node.start(width, height);
+        consumer.accept(c);
+        node.end(c);
+        return ThreadedRenderer.createHardwareBitmap(node, width, height);
+    }
+}
diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 3f93f76..f6fab86 100644
--- a/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -17,11 +17,19 @@
 package com.android.systemui.shared.system;
 
 import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityOptions;
 import android.app.AppGlobals;
+import android.app.IAssistDataReceiver;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -32,15 +40,21 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.IconDrawableFactory;
 import android.util.Log;
 
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 
 public class ActivityManagerWrapper {
 
@@ -50,11 +64,15 @@
 
     private final PackageManager mPackageManager;
     private final IconDrawableFactory mDrawableFactory;
+    private final BackgroundExecutor mBackgroundExecutor;
+    private final TaskStackChangeListeners mTaskStackChangeListeners;
 
     private ActivityManagerWrapper() {
         final Context context = AppGlobals.getInitialApplication();
         mPackageManager = context.getPackageManager();
         mDrawableFactory = IconDrawableFactory.newInstance(context);
+        mBackgroundExecutor = BackgroundExecutor.get();
+        mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
     }
 
     public static ActivityManagerWrapper getInstance() {
@@ -75,6 +93,25 @@
     }
 
     /**
+     * @return the top running task (can be {@code null}).
+     */
+    public ActivityManager.RunningTaskInfo getRunningTask() {
+        // Note: The set of running tasks from the system is ordered by recency
+        try {
+            List<ActivityManager.RunningTaskInfo> tasks =
+                    ActivityManager.getService().getFilteredTasks(1,
+                            ACTIVITY_TYPE_RECENTS /* ignoreActivityType */,
+                            WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
+            if (tasks.isEmpty()) {
+                return null;
+            }
+            return tasks.get(0);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * @return a list of the recents tasks.
      */
     public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
@@ -198,4 +235,186 @@
         }
         return label;
     }
+
+    /**
+     * Starts the recents activity. The caller should manage the thread on which this is called.
+     */
+    public void startRecentsActivity(AssistDataReceiverCompat assistDataReceiver, Bundle options,
+            ActivityOptions opts, int userId, Consumer<Boolean> resultCallback,
+            Handler resultCallbackHandler) {
+        Bundle activityOptions = opts != null ? opts.toBundle() : null;
+        try {
+            IAssistDataReceiver receiver = null;
+            if (assistDataReceiver != null) {
+                receiver = new IAssistDataReceiver.Stub() {
+                    public void onHandleAssistData(Bundle resultData) {
+                        assistDataReceiver.onHandleAssistData(resultData);
+                    }
+                    public void onHandleAssistScreenshot(Bitmap screenshot) {
+                        assistDataReceiver.onHandleAssistScreenshot(screenshot);
+                    }
+                };
+            }
+            ActivityManager.getService().startRecentsActivity(receiver, options, activityOptions,
+                    userId);
+            if (resultCallback != null) {
+                resultCallbackHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        resultCallback.accept(true);
+                    }
+                });
+            }
+        } catch (Exception e) {
+            if (resultCallback != null) {
+                resultCallbackHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        resultCallback.accept(false);
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * Starts a task from Recents.
+     *
+     * @see {@link #startActivityFromRecentsAsync(TaskKey, ActivityOptions, int, int, Consumer, Handler)}
+     */
+    public void startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options,
+            Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
+        startActivityFromRecentsAsync(taskKey, options, WINDOWING_MODE_UNDEFINED,
+                ACTIVITY_TYPE_UNDEFINED, resultCallback, resultCallbackHandler);
+    }
+
+    /**
+     * Starts a task from Recents.
+     *
+     * @param resultCallback The result success callback
+     * @param resultCallbackHandler The handler to receive the result callback
+     */
+    public void startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options,
+            int windowingMode, int activityType, Consumer<Boolean> resultCallback,
+            Handler resultCallbackHandler) {
+        if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            // We show non-visible docked tasks in Recents, but we always want to launch
+            // them in the fullscreen stack.
+            if (options == null) {
+                options = ActivityOptions.makeBasic();
+            }
+            options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        } else if (windowingMode != WINDOWING_MODE_UNDEFINED
+                || activityType != ACTIVITY_TYPE_UNDEFINED) {
+            if (options == null) {
+                options = ActivityOptions.makeBasic();
+            }
+            options.setLaunchWindowingMode(windowingMode);
+            options.setLaunchActivityType(activityType);
+        }
+        final ActivityOptions finalOptions = options;
+
+        // Execute this from another thread such that we can do other things (like caching the
+        // bitmap for the thumbnail) while AM is busy starting our activity.
+        mBackgroundExecutor.submit(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    ActivityManager.getService().startActivityFromRecents(taskKey.id,
+                            finalOptions == null ? null : finalOptions.toBundle());
+                    if (resultCallback != null) {
+                        resultCallbackHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                resultCallback.accept(true);
+                            }
+                        });
+                    }
+                } catch (Exception e) {
+                    if (resultCallback != null) {
+                        resultCallbackHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                resultCallback.accept(false);
+                            }
+                        });
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Registers a task stack listener with the system.
+     * This should be called on the main thread.
+     */
+    public void registerTaskStackListener(TaskStackChangeListener listener) {
+        synchronized (mTaskStackChangeListeners) {
+            mTaskStackChangeListeners.addListener(ActivityManager.getService(), listener);
+        }
+    }
+
+    /**
+     * Unregisters a task stack listener with the system.
+     * This should be called on the main thread.
+     */
+    public void unregisterTaskStackListener(TaskStackChangeListener listener) {
+        synchronized (mTaskStackChangeListeners) {
+            mTaskStackChangeListeners.removeListener(listener);
+        }
+    }
+
+    /**
+     * Requests that the system close any open system windows (including other SystemUI).
+     */
+    public void closeSystemWindows(String reason) {
+        mBackgroundExecutor.submit(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    ActivityManager.getService().closeSystemDialogs(reason);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to close system windows", e);
+                }
+            }
+        });
+    }
+
+    /**
+     * Removes a task by id.
+     */
+    public void removeTask(int taskId) {
+        mBackgroundExecutor.submit(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    ActivityManager.getService().removeTask(taskId);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to remove task=" + taskId, e);
+                }
+            }
+        });
+    }
+
+    /**
+     * Cancels the current window transtion to/from Recents for the given task id.
+     */
+    public void cancelWindowTransition(int taskId) {
+        try {
+            ActivityManager.getService().cancelTaskWindowTransition(taskId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
+        }
+    }
+
+    /**
+     * Cancels the current thumbnail transtion to/from Recents for the given task id.
+     */
+    public void cancelThumbnailTransition(int taskId) {
+        try {
+            ActivityManager.getService().cancelTaskThumbnailTransition(taskId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to cancel window transition for task=" + taskId, e);
+        }
+    }
 }
diff --git a/android/telephony/ims/feature/IRcsFeature.java b/com/android/systemui/shared/system/AssistDataReceiverCompat.java
similarity index 65%
copy from android/telephony/ims/feature/IRcsFeature.java
copy to com/android/systemui/shared/system/AssistDataReceiverCompat.java
index e28e1b3..cd943f6 100644
--- a/android/telephony/ims/feature/IRcsFeature.java
+++ b/com/android/systemui/shared/system/AssistDataReceiverCompat.java
@@ -14,13 +14,15 @@
  * limitations under the License
  */
 
-package android.telephony.ims.feature;
+package com.android.systemui.shared.system;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
 
 /**
- * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added
- * in the framework.
- * @hide
+ * Abstract class for assist data receivers.
  */
-
-public interface IRcsFeature {
+public abstract class AssistDataReceiverCompat {
+    public abstract void onHandleAssistData(Bundle resultData);
+    public abstract void onHandleAssistScreenshot(Bitmap screenshot);
 }
diff --git a/com/android/systemui/shared/system/BackgroundExecutor.java b/com/android/systemui/shared/system/BackgroundExecutor.java
new file mode 100644
index 0000000..cfd1f9a
--- /dev/null
+++ b/com/android/systemui/shared/system/BackgroundExecutor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.system;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * Offloads work from other threads by running it in a background thread.
+ */
+public class BackgroundExecutor {
+
+    private static final BackgroundExecutor sInstance = new BackgroundExecutor();
+
+    private final ExecutorService mExecutorService = Executors.newFixedThreadPool(2);
+
+    /**
+     * @return the static instance of the background executor.
+     */
+    public static BackgroundExecutor get() {
+        return sInstance;
+    }
+
+    /**
+     * Runs the given {@param runnable} on one of the background executor threads.
+     */
+    public Future<?> submit(Runnable runnable) {
+        return mExecutorService.submit(runnable);
+    }
+
+    /**
+     * Runs the given {@param runnable} on one of the background executor threads. Return
+     * {@param result} when the future is resolved.
+     */
+    public <T> Future<T> submit(Runnable runnable, T result) {
+        return mExecutorService.submit(runnable, result);
+    }
+}
diff --git a/com/android/systemui/recents/misc/TaskStackChangeListener.java b/com/android/systemui/shared/system/TaskStackChangeListener.java
similarity index 65%
rename from com/android/systemui/recents/misc/TaskStackChangeListener.java
rename to com/android/systemui/shared/system/TaskStackChangeListener.java
index 6d0952a..17cb0d8 100644
--- a/com/android/systemui/recents/misc/TaskStackChangeListener.java
+++ b/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -14,26 +14,26 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.system;
 
 import android.app.ActivityManager.TaskSnapshot;
-import android.content.Context;
 import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
 /**
- * An abstract class to track task stack changes.
- * Classes should implement this instead of {@link android.app.ITaskStackListener}
- * to reduce IPC calls from system services. These callbacks will be called on the main thread.
+ * An interface to track task stack changes. Classes should implement this instead of
+ * {@link android.app.ITaskStackListener} to reduce IPC calls from system services.
  */
 public abstract class TaskStackChangeListener {
 
-    /**
-     * NOTE: This call is made of the thread that the binder call comes in on.
-     */
+    // Binder thread callbacks
     public void onTaskStackChangedBackground() { }
+
+    // Main thread callbacks
     public void onTaskStackChanged() { }
-    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
+    public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { }
     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
     public void onActivityUnpinned() { }
     public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
@@ -45,17 +45,16 @@
     public void onTaskProfileLocked(int taskId, int userId) { }
 
     /**
-     * Checks that the current user matches the user's SystemUI process. Since
+     * Checks that the current user matches the process. Since
      * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
-     * TaskStackChangeListener should make this call to verify that we don't act on events from other
-     * user's processes.
+     * {@link TaskStackChangeListener} should make this call to verify that we don't act on events
+     * originating from another user's interactions.
      */
-    protected final boolean checkCurrentUserId(Context context, boolean debug) {
+    protected final boolean checkCurrentUserId(int currentUserId, boolean debug) {
         int processUserId = UserHandle.myUserId();
-        int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
         if (processUserId != currentUserId) {
             if (debug) {
-                Log.d(SystemServicesProxy.TAG, "UID mismatch. SystemUI is running uid=" + processUserId
+                Log.d("TaskStackChangeListener", "UID mismatch. Process is uid=" + processUserId
                         + " and the current user is uid=" + currentUserId);
             }
             return false;
diff --git a/com/android/systemui/recents/misc/TaskStackChangeListeners.java b/com/android/systemui/shared/system/TaskStackChangeListeners.java
similarity index 95%
rename from com/android/systemui/recents/misc/TaskStackChangeListeners.java
rename to com/android/systemui/shared/system/TaskStackChangeListeners.java
index 8eb70f0..81c37a9 100644
--- a/com/android/systemui/recents/misc/TaskStackChangeListeners.java
+++ b/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.recents.misc;
+package com.android.systemui.shared.system;
 
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.IActivityManager;
@@ -26,6 +26,8 @@
 import android.os.Trace;
 import android.util.Log;
 
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -43,6 +45,7 @@
     private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>();
 
     private final Handler mHandler;
+    private boolean mRegistered;
 
     public TaskStackChangeListeners(Looper looper) {
         mHandler = new H(looper);
@@ -50,16 +53,21 @@
 
     public void addListener(IActivityManager am, TaskStackChangeListener listener) {
         mTaskStackListeners.add(listener);
-        if (mTaskStackListeners.size() == 1) {
+        if (!mRegistered) {
             // Register mTaskStackListener to IActivityManager only once if needed.
             try {
                 am.registerTaskStackListener(this);
+                mRegistered = true;
             } catch (Exception e) {
                 Log.w(TAG, "Failed to call registerTaskStackListener", e);
             }
         }
     }
 
+    public void removeListener(TaskStackChangeListener listener) {
+        mTaskStackListeners.remove(listener);
+    }
+
     @Override
     public void onTaskStackChanged() throws RemoteException {
         // Call the task changed callback for the non-ui thread listeners first
@@ -170,7 +178,7 @@
                         Trace.beginSection("onTaskSnapshotChanged");
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                             mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
-                                    (TaskSnapshot) msg.obj);
+                                    new ThumbnailData((TaskSnapshot) msg.obj));
                         }
                         Trace.endSection();
                         break;
diff --git a/com/android/systemui/shared/system/WindowManagerWrapper.java b/com/android/systemui/shared/system/WindowManagerWrapper.java
new file mode 100644
index 0000000..1477558
--- /dev/null
+++ b/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.graphics.Rect;
+import android.view.WindowManagerGlobal;
+
+public class WindowManagerWrapper {
+
+    private static final String TAG = "WindowManagerWrapper";
+
+    private static final WindowManagerWrapper sInstance = new WindowManagerWrapper();
+
+    public static WindowManagerWrapper getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * @return the stable insets for the primary display.
+     */
+    public void getStableInsets(Rect outStableInsets) {
+        try {
+            WindowManagerGlobal.getWindowManagerService().getStableInsets(DEFAULT_DISPLAY,
+                    outStableInsets);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 195f4d3..1cda301 100644
--- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -16,8 +16,8 @@
 
 package com.android.systemui.shortcut;
 
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.os.UserHandle.USER_CURRENT;
 
 import android.app.ActivityManager;
@@ -92,8 +92,8 @@
                 // If there is no window docked, we dock the top-most window.
                 Recents recents = getComponent(Recents.class);
                 int dockMode = (shortcutCode == SC_DOCK_LEFT)
-                        ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
-                        : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+                        ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                        : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
                 List<ActivityManager.RecentTaskInfo> taskList =
                         ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT);
                 recents.showRecentApps(
diff --git a/com/android/systemui/stackdivider/DividerView.java b/com/android/systemui/stackdivider/DividerView.java
index 7bcef57..1596d12 100644
--- a/com/android/systemui/stackdivider/DividerView.java
+++ b/com/android/systemui/stackdivider/DividerView.java
@@ -439,7 +439,7 @@
         if (mMinimizedSnapAlgorithm == null) {
             mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
                     mDisplayWidth, mDisplayHeight, mDividerSize, isHorizontalDivision(),
-                    mStableInsets, mDockedStackMinimized && mHomeStackResizable);
+                    mStableInsets, mDockSide, mDockedStackMinimized && mHomeStackResizable);
         }
     }
 
diff --git a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index 0997983..826fa6c 100644
--- a/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -31,8 +31,9 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
 import com.android.systemui.recents.events.component.ShowUserToastEvent;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.events.StartedDragingEvent;
 import com.android.systemui.stackdivider.events.StoppedDragingEvent;
 
@@ -75,8 +76,8 @@
     public ForcedResizableInfoActivityController(Context context) {
         mContext = context;
         EventBus.getDefault().register(this);
-        SystemServicesProxy.getInstance(context).registerTaskStackListener(
-                new TaskStackChangeListener() {
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(
+                new SysUiTaskStackChangeListener() {
                     @Override
                     public void onActivityForcedResizable(String packageName, int taskId,
                             int reason) {
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 6c5f4b2..8ff950e 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -69,6 +69,7 @@
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -453,6 +454,11 @@
         } else {
             headsUpheight = mMaxHeadsUpHeight;
         }
+        NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
+                NotificationContentView.VISIBLE_TYPE_HEADSUP);
+        if (headsUpWrapper != null) {
+            headsUpheight = Math.max(headsUpheight, headsUpWrapper.getMinLayoutHeight());
+        }
         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
                 mNotificationAmbientHeight);
     }
@@ -1256,16 +1262,21 @@
     }
 
     private void initDimens() {
-        mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
-        mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
-        mNotificationMinHeightLarge = getFontScaledHeight(
+        mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_min_height_legacy);
+        mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_min_height);
+        mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height_increased);
-        mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
-        mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
-        mMaxHeadsUpHeightLegacy = getFontScaledHeight(
+        mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_max_height);
+        mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_ambient_height);
+        mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_legacy);
-        mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
-        mMaxHeadsUpHeightIncreased = getFontScaledHeight(
+        mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_max_heads_up_height);
+        mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_increased);
 
         Resources res = getResources();
@@ -1280,17 +1291,6 @@
     }
 
     /**
-     * @param dimenId the dimen to look up
-     * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
-     */
-    private int getFontScaledHeight(int dimenId) {
-        int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
-        float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
-                getResources().getDisplayMetrics().density);
-        return (int) (dimensionPixelSize * factor);
-    }
-
-    /**
      * Resets this view so it can be re-used for an updated notification.
      */
     public void reset() {
diff --git a/com/android/systemui/statusbar/NotificationGuts.java b/com/android/systemui/statusbar/NotificationGuts.java
index 54d622b..c4024a5 100644
--- a/com/android/systemui/statusbar/NotificationGuts.java
+++ b/com/android/systemui/statusbar/NotificationGuts.java
@@ -18,46 +18,21 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.app.INotificationManager;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.Switch;
-import android.widget.TextView;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
-import java.util.Set;
-
 /**
  * The guts of a notification revealed when performing a long press.
  */
diff --git a/com/android/systemui/statusbar/NotificationGutsManager.java b/com/android/systemui/statusbar/NotificationGutsManager.java
new file mode 100644
index 0000000..b585bdf
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.Interpolators;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
+ * closing guts, and keeping track of the currently exposed notification guts.
+ */
+public class NotificationGutsManager implements Dumpable {
+    private static final String TAG = "NotificationGutsManager";
+
+    // Must match constant in Settings. Used to highlight preferences when linking to Settings.
+    private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+
+    private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+    private final Set<String> mNonBlockablePkgs;
+    private final NotificationPresenter mPresenter;
+    // TODO: Create NotificationListContainer interface and use it instead of
+    // NotificationStackScrollLayout here
+    private final NotificationStackScrollLayout mStackScroller;
+    private final Context mContext;
+    private final AccessibilityManager mAccessibilityManager;
+    // which notification is currently being longpress-examined by the user
+    private NotificationGuts mNotificationGutsExposed;
+    private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
+    private final NotificationInfo.CheckSaveListener mCheckSaveListener;
+    private String mKeyToRemoveOnGutsClosed;
+
+    public NotificationGutsManager(
+            NotificationPresenter presenter,
+            NotificationStackScrollLayout stackScroller,
+            NotificationInfo.CheckSaveListener checkSaveListener,
+            Context context) {
+        mPresenter = presenter;
+        mStackScroller = stackScroller;
+        mCheckSaveListener = checkSaveListener;
+        mContext = context;
+        Resources res = context.getResources();
+
+        mNonBlockablePkgs = new HashSet<>();
+        Collections.addAll(mNonBlockablePkgs, res.getStringArray(
+                com.android.internal.R.array.config_nonBlockableNotificationPackages));
+
+        mAccessibilityManager = (AccessibilityManager)
+                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+    }
+
+    public String getKeyToRemoveOnGutsClosed() {
+        return mKeyToRemoveOnGutsClosed;
+    }
+
+    public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) {
+        mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed;
+    }
+
+    private void saveAndCloseNotificationMenu(
+            ExpandableNotificationRow row, NotificationGuts guts, View done) {
+        guts.resetFalsingCheck();
+        int[] rowLocation = new int[2];
+        int[] doneLocation = new int[2];
+        row.getLocationOnScreen(rowLocation);
+        done.getLocationOnScreen(doneLocation);
+
+        final int centerX = done.getWidth() / 2;
+        final int centerY = done.getHeight() / 2;
+        final int x = doneLocation[0] - rowLocation[0] + centerX;
+        final int y = doneLocation[1] - rowLocation[1] + centerY;
+        closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+                true /* removeControls */, x, y, true /* resetMenu */);
+    }
+
+    /**
+     * Sends an intent to open the notification settings for a particular package and optional
+     * channel.
+     */
+    private void startAppNotificationSettingsActivity(String packageName, final int appUid,
+            final NotificationChannel channel) {
+        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
+        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+        if (channel != null) {
+            intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
+        }
+        mPresenter.startNotificationGutsIntent(intent, appUid);
+    }
+
+    public void bindGuts(final ExpandableNotificationRow row) {
+        bindGuts(row, mGutsMenuItem);
+    }
+
+    private void bindGuts(final ExpandableNotificationRow row,
+            NotificationMenuRowPlugin.MenuItem item) {
+        row.inflateGuts();
+        row.setGutsView(item);
+        final StatusBarNotification sbn = row.getStatusBarNotification();
+        row.setTag(sbn.getPackageName());
+        final NotificationGuts guts = row.getGuts();
+        guts.setClosedListener((NotificationGuts g) -> {
+            if (!g.willBeRemoved() && !row.isRemoved()) {
+                mStackScroller.onHeightChanged(
+                        row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
+            }
+            if (mNotificationGutsExposed == g) {
+                mNotificationGutsExposed = null;
+                mGutsMenuItem = null;
+            }
+            String key = sbn.getKey();
+            if (key.equals(mKeyToRemoveOnGutsClosed)) {
+                mKeyToRemoveOnGutsClosed = null;
+                mPresenter.removeNotification(key, mPresenter.getLatestRankingMap());
+            }
+        });
+
+        View gutsView = item.getGutsView();
+        if (gutsView instanceof NotificationSnooze) {
+            NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
+            snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+            snoozeGuts.setStatusBarNotification(sbn);
+            snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
+            guts.setHeightChangedListener((NotificationGuts g) -> {
+                mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+            });
+        }
+
+        if (gutsView instanceof NotificationInfo) {
+            final UserHandle userHandle = sbn.getUser();
+            PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+                    userHandle.getIdentifier());
+            final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+            final String pkg = sbn.getPackageName();
+            NotificationInfo info = (NotificationInfo) gutsView;
+            // Settings link is only valid for notifications that specify a user, unless this is the
+            // system user.
+            NotificationInfo.OnSettingsClickListener onSettingsClick = null;
+            if (!userHandle.equals(UserHandle.ALL)
+                    || mPresenter.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+                onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+                    mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+                    guts.resetFalsingCheck();
+                    startAppNotificationSettingsActivity(pkg, appUid, channel);
+                };
+            }
+            final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
+                    Intent intent) -> {
+                mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+                guts.resetFalsingCheck();
+                mPresenter.startNotificationGutsIntent(intent, sbn.getUid());
+            };
+            final View.OnClickListener onDoneClick = (View v) -> {
+                saveAndCloseNotificationMenu(row, guts, v);
+            };
+
+            ArraySet<NotificationChannel> channels = new ArraySet<>();
+            channels.add(row.getEntry().channel);
+            if (row.isSummaryWithChildren()) {
+                // If this is a summary, then add in the children notification channels for the
+                // same user and pkg.
+                final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
+                final int numChildren = childrenRows.size();
+                for (int i = 0; i < numChildren; i++) {
+                    final ExpandableNotificationRow childRow = childrenRows.get(i);
+                    final NotificationChannel childChannel = childRow.getEntry().channel;
+                    final StatusBarNotification childSbn = childRow.getStatusBarNotification();
+                    if (childSbn.getUser().equals(userHandle) &&
+                            childSbn.getPackageName().equals(pkg)) {
+                        channels.add(childChannel);
+                    }
+                }
+            }
+            try {
+                info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
+                        row.getEntry().channel.getImportance(), sbn, onSettingsClick,
+                        onAppSettingsClick, onDoneClick, mCheckSaveListener,
+                        mNonBlockablePkgs);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        }
+    }
+
+    /**
+     * Closes guts or notification menus that might be visible and saves any changes.
+     *
+     * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
+     * @param force true if guts should be closed regardless of state (used for snooze only).
+     * @param removeControls true if controls (e.g. info) should be closed.
+     * @param x if closed based on touch location, this is the x touch location.
+     * @param y if closed based on touch location, this is the y touch location.
+     * @param resetMenu if any notification menus that might be revealed should be closed.
+     */
+    public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
+            int x, int y, boolean resetMenu) {
+        if (mNotificationGutsExposed != null) {
+            mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
+        }
+        if (resetMenu) {
+            mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
+        }
+    }
+
+    /**
+     * Returns the exposed NotificationGuts or null if none are exposed.
+     */
+    public NotificationGuts getExposedGuts() {
+        return mNotificationGutsExposed;
+    }
+
+    public void setExposedGuts(NotificationGuts guts) {
+        mNotificationGutsExposed = guts;
+    }
+
+    /**
+     *  Opens guts on the given ExpandableNotificationRow |v|.
+     *
+     * @param v ExpandableNotificationRow to open guts on
+     * @param x x coordinate of origin of circular reveal
+     * @param y y coordinate of origin of circular reveal
+     * @param item MenuItem the guts should display
+     * @return true if guts was opened
+     */
+    public boolean openGuts(View v, int x, int y,
+            NotificationMenuRowPlugin.MenuItem item) {
+        if (!(v instanceof ExpandableNotificationRow)) {
+            return false;
+        }
+
+        if (v.getWindowToken() == null) {
+            Log.e(TAG, "Trying to show notification guts, but not attached to window");
+            return false;
+        }
+
+        final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+        if (row.isDark()) {
+            return false;
+        }
+        v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        if (row.areGutsExposed()) {
+            closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+                    true /* removeControls */, -1 /* x */, -1 /* y */,
+                    true /* resetMenu */);
+            return false;
+        }
+        bindGuts(row, item);
+        NotificationGuts guts = row.getGuts();
+
+        // Assume we are a status_bar_notification_row
+        if (guts == null) {
+            // This view has no guts. Examples are the more card or the dismiss all view
+            return false;
+        }
+
+        mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
+
+        // ensure that it's laid but not visible until actually laid out
+        guts.setVisibility(View.INVISIBLE);
+        // Post to ensure the the guts are properly laid out.
+        guts.post(new Runnable() {
+            @Override
+            public void run() {
+                if (row.getWindowToken() == null) {
+                    Log.e(TAG, "Trying to show notification guts, but not attached to "
+                            + "window");
+                    return;
+                }
+                closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+                        true /* removeControls */, -1 /* x */, -1 /* y */,
+                        false /* resetMenu */);
+                guts.setVisibility(View.VISIBLE);
+                final double horz = Math.max(guts.getWidth() - x, x);
+                final double vert = Math.max(guts.getHeight() - y, y);
+                final float r = (float) Math.hypot(horz, vert);
+                final Animator a
+                        = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
+                a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+                a.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        super.onAnimationEnd(animation);
+                        // Move the notification view back over the menu
+                        row.resetTranslation();
+                    }
+                });
+                a.start();
+                final boolean needsFalsingProtection =
+                        (mPresenter.isPresenterLocked() &&
+                                !mAccessibilityManager.isTouchExplorationEnabled());
+                guts.setExposed(true /* exposed */, needsFalsingProtection);
+                row.closeRemoteInput();
+                mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+                mNotificationGutsExposed = guts;
+                mGutsMenuItem = item;
+            }
+        });
+        return true;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("mKeyToRemoveOnGutsClosed: ");
+        pw.println(mKeyToRemoveOnGutsClosed);
+    }
+}
diff --git a/com/android/systemui/statusbar/NotificationInfo.java b/com/android/systemui/statusbar/NotificationInfo.java
index 3b23a0c..8d1bb5f 100644
--- a/com/android/systemui/statusbar/NotificationInfo.java
+++ b/com/android/systemui/statusbar/NotificationInfo.java
@@ -84,7 +84,7 @@
     public interface CheckSaveListener {
         // Invoked when importance has changed and the NotificationInfo wants to try to save it.
         // Listener should run saveImportance unless the change should be canceled.
-        void checkSave(Runnable saveImportance);
+        void checkSave(Runnable saveImportance, StatusBarNotification sbn);
     }
 
     public interface OnSettingsClickListener {
@@ -409,7 +409,7 @@
     public boolean handleCloseControls(boolean save, boolean force) {
         if (save && hasImportanceChanged()) {
             if (mCheckSaveListener != null) {
-                mCheckSaveListener.checkSave(() -> { saveImportance(); });
+                mCheckSaveListener.checkSave(this::saveImportance, mSbn);
             } else {
                 saveImportance();
             }
diff --git a/com/android/systemui/statusbar/NotificationMediaManager.java b/com/android/systemui/statusbar/NotificationMediaManager.java
new file mode 100644
index 0000000..e65bab2
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -0,0 +1,245 @@
+package com.android.systemui.statusbar;
+
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.systemui.Dumpable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles tasks and state related to media notifications. For example, there is a 'current' media
+ * notification, which this class keeps track of.
+ */
+public class NotificationMediaManager implements Dumpable {
+    private static final String TAG = "NotificationMediaManager";
+    public static final boolean DEBUG_MEDIA = false;
+
+    private final NotificationPresenter mPresenter;
+    private final Context mContext;
+    private final MediaSessionManager mMediaSessionManager;
+    private MediaController mMediaController;
+    private String mMediaNotificationKey;
+    private MediaMetadata mMediaMetadata;
+
+    private final MediaController.Callback mMediaListener = new MediaController.Callback() {
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            super.onPlaybackStateChanged(state);
+            if (DEBUG_MEDIA) {
+                Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
+            }
+            if (state != null) {
+                if (!isPlaybackActive(state.getState())) {
+                    clearCurrentMediaNotification();
+                    mPresenter.updateMediaMetaData(true, true);
+                }
+            }
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            super.onMetadataChanged(metadata);
+            if (DEBUG_MEDIA) {
+                Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
+            }
+            mMediaMetadata = metadata;
+            mPresenter.updateMediaMetaData(true, true);
+        }
+    };
+
+    public NotificationMediaManager(NotificationPresenter presenter, Context context) {
+        mPresenter = presenter;
+        mContext = context;
+        mMediaSessionManager
+                = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+        // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
+        // in session state
+    }
+
+    public void onNotificationRemoved(String key) {
+        if (key.equals(mMediaNotificationKey)) {
+            clearCurrentMediaNotification();
+            mPresenter.updateMediaMetaData(true, true);
+        }
+    }
+
+    public String getMediaNotificationKey() {
+        return mMediaNotificationKey;
+    }
+
+    public MediaMetadata getMediaMetadata() {
+        return mMediaMetadata;
+    }
+
+    public void findAndUpdateMediaNotifications() {
+        boolean metaDataChanged = false;
+
+        synchronized (mPresenter.getNotificationData()) {
+            ArrayList<NotificationData.Entry> activeNotifications = mPresenter
+                    .getNotificationData().getActiveNotifications();
+            final int N = activeNotifications.size();
+
+            // Promote the media notification with a controller in 'playing' state, if any.
+            NotificationData.Entry mediaNotification = null;
+            MediaController controller = null;
+            for (int i = 0; i < N; i++) {
+                final NotificationData.Entry entry = activeNotifications.get(i);
+
+                if (isMediaNotification(entry)) {
+                    final MediaSession.Token token =
+                            entry.notification.getNotification().extras.getParcelable(
+                                    Notification.EXTRA_MEDIA_SESSION);
+                    if (token != null) {
+                        MediaController aController = new MediaController(mContext, token);
+                        if (PlaybackState.STATE_PLAYING ==
+                                getMediaControllerPlaybackState(aController)) {
+                            if (DEBUG_MEDIA) {
+                                Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
+                                        + entry.notification.getKey());
+                            }
+                            mediaNotification = entry;
+                            controller = aController;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (mediaNotification == null) {
+                // Still nothing? OK, let's just look for live media sessions and see if they match
+                // one of our notifications. This will catch apps that aren't (yet!) using media
+                // notifications.
+
+                if (mMediaSessionManager != null) {
+                    // TODO: Should this really be for all users?
+                    final List<MediaController> sessions
+                            = mMediaSessionManager.getActiveSessionsForUser(
+                            null,
+                            UserHandle.USER_ALL);
+
+                    for (MediaController aController : sessions) {
+                        if (PlaybackState.STATE_PLAYING ==
+                                getMediaControllerPlaybackState(aController)) {
+                            // now to see if we have one like this
+                            final String pkg = aController.getPackageName();
+
+                            for (int i = 0; i < N; i++) {
+                                final NotificationData.Entry entry = activeNotifications.get(i);
+                                if (entry.notification.getPackageName().equals(pkg)) {
+                                    if (DEBUG_MEDIA) {
+                                        Log.v(TAG, "DEBUG_MEDIA: found controller matching "
+                                                + entry.notification.getKey());
+                                    }
+                                    controller = aController;
+                                    mediaNotification = entry;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (controller != null && !sameSessions(mMediaController, controller)) {
+                // We have a new media session
+                clearCurrentMediaNotification();
+                mMediaController = controller;
+                mMediaController.registerCallback(mMediaListener);
+                mMediaMetadata = mMediaController.getMetadata();
+                if (DEBUG_MEDIA) {
+                    Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
+                            + mMediaMetadata);
+                }
+
+                if (mediaNotification != null) {
+                    mMediaNotificationKey = mediaNotification.notification.getKey();
+                    if (DEBUG_MEDIA) {
+                        Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
+                                + mMediaNotificationKey + " controller=" + mMediaController);
+                    }
+                }
+                metaDataChanged = true;
+            }
+        }
+
+        if (metaDataChanged) {
+            mPresenter.updateNotifications();
+        }
+        mPresenter.updateMediaMetaData(metaDataChanged, true);
+    }
+
+    public void clearCurrentMediaNotification() {
+        mMediaNotificationKey = null;
+        mMediaMetadata = null;
+        if (mMediaController != null) {
+            if (DEBUG_MEDIA) {
+                Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+                        + mMediaController.getPackageName());
+            }
+            mMediaController.unregisterCallback(mMediaListener);
+        }
+        mMediaController = null;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("    mMediaSessionManager=");
+        pw.println(mMediaSessionManager);
+        pw.print("    mMediaNotificationKey=");
+        pw.println(mMediaNotificationKey);
+        pw.print("    mMediaController=");
+        pw.print(mMediaController);
+        if (mMediaController != null) {
+            pw.print(" state=" + mMediaController.getPlaybackState());
+        }
+        pw.println();
+        pw.print("    mMediaMetadata=");
+        pw.print(mMediaMetadata);
+        if (mMediaMetadata != null) {
+            pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
+        }
+        pw.println();
+    }
+
+    private boolean isPlaybackActive(int state) {
+        return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
+                && state != PlaybackState.STATE_NONE;
+    }
+
+    private boolean sameSessions(MediaController a, MediaController b) {
+        if (a == b) {
+            return true;
+        }
+        if (a == null) {
+            return false;
+        }
+        return a.controlsSameSession(b);
+    }
+
+    private int getMediaControllerPlaybackState(MediaController controller) {
+        if (controller != null) {
+            final PlaybackState playbackState = controller.getPlaybackState();
+            if (playbackState != null) {
+                return playbackState.getState();
+            }
+        }
+        return PlaybackState.STATE_NONE;
+    }
+
+    private boolean isMediaNotification(NotificationData.Entry entry) {
+        // TODO: confirm that there's a valid media key
+        return entry.getExpandedContentView() != null &&
+                entry.getExpandedContentView()
+                        .findViewById(com.android.internal.R.id.media_actions) != null;
+    }
+}
diff --git a/com/android/systemui/statusbar/NotificationPresenter.java b/com/android/systemui/statusbar/NotificationPresenter.java
new file mode 100644
index 0000000..1aca60c
--- /dev/null
+++ b/com/android/systemui/statusbar/NotificationPresenter.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import android.content.Intent;
+import android.service.notification.NotificationListenerService;
+
+/**
+ * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
+ * for both querying the state of the system (some modularised piece of functionality may
+ * want to act differently based on e.g. whether the presenter is visible to the user or not) and
+ * for affecting the state of the system (e.g. starting an intent, given that the presenter may
+ * want to perform some action before doing so).
+ */
+public interface NotificationPresenter {
+
+    /**
+     * Returns true if the presenter is not visible. For example, it may not be necessary to do
+     * animations if this returns true.
+     */
+    boolean isPresenterFullyCollapsed();
+
+    /**
+     * Returns true if the presenter is locked. For example, if the keyguard is active.
+     */
+    boolean isPresenterLocked();
+
+    /**
+     * Returns the current user id. This can change if the user is switched.
+     */
+    int getCurrentUserId();
+
+    /**
+     * Runs the given intent. The presenter may want to run some animations or close itself when
+     * this happens.
+     */
+    void startNotificationGutsIntent(Intent intent, int appUid);
+
+    /**
+     * Returns NotificationData.
+     */
+    NotificationData getNotificationData();
+
+    // TODO: Create NotificationEntryManager and move this method to there.
+    /**
+     * Signals that some notifications have changed, and NotificationPresenter should update itself.
+     */
+    void updateNotifications();
+
+    /**
+     * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
+     */
+    void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
+
+    // TODO: Create NotificationUpdateHandler and move this method to there.
+    /**
+     * Removes a notification.
+     */
+    void removeNotification(String key, NotificationListenerService.RankingMap ranking);
+
+    // TODO: Create NotificationEntryManager and move this method to there.
+    /**
+     * Gets the latest ranking map.
+     */
+    NotificationListenerService.RankingMap getLatestRankingMap();
+}
diff --git a/com/android/systemui/statusbar/OperatorNameView.java b/com/android/systemui/statusbar/OperatorNameView.java
new file mode 100644
index 0000000..5090f74
--- /dev/null
+++ b/com/android/systemui/statusbar/OperatorNameView.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.List;
+
+public class OperatorNameView extends TextView implements DemoMode, DarkReceiver,
+        SignalCallback, Tunable {
+
+    private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
+
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private boolean mDemoMode;
+
+    private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onRefreshCarrierInfo() {
+            updateText();
+        }
+    };
+
+    public OperatorNameView(Context context) {
+        this(context, null);
+    }
+
+    public OperatorNameView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public OperatorNameView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor.registerCallback(mCallback);
+        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this);
+        Dependency.get(NetworkController.class).addCallback(this);
+        Dependency.get(TunerService.class).addTunable(this, KEY_SHOW_OPERATOR_NAME);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mKeyguardUpdateMonitor.removeCallback(mCallback);
+        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(this);
+        Dependency.get(NetworkController.class).removeCallback(this);
+        Dependency.get(TunerService.class).removeTunable(this);
+    }
+
+    @Override
+    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+        setTextColor(DarkIconDispatcher.getTint(area, this, tint));
+    }
+
+    @Override
+    public void setIsAirplaneMode(IconState icon) {
+        update();
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        update();
+    }
+
+    @Override
+    public void dispatchDemoCommand(String command, Bundle args) {
+        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
+            mDemoMode = true;
+        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
+            mDemoMode = false;
+            update();
+        } else if (mDemoMode && command.equals(COMMAND_OPERATOR)) {
+            setText(args.getString("name"));
+        }
+    }
+
+    private void update() {
+        boolean showOperatorName = Dependency.get(TunerService.class)
+                .getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0;
+        setVisibility(showOperatorName ? VISIBLE : GONE);
+
+        boolean hasMobile = ConnectivityManager.from(mContext)
+                .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
+        if (!hasMobile || airplaneMode) {
+            setText(null);
+            setVisibility(GONE);
+            return;
+        }
+
+        if (!mDemoMode) {
+            updateText();
+        }
+    }
+
+    private void updateText() {
+        CharSequence displayText = null;
+        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+        final int N = subs.size();
+        for (int i = 0; i < N; i++) {
+            int subId = subs.get(i).getSubscriptionId();
+            State simState = mKeyguardUpdateMonitor.getSimState(subId);
+            CharSequence carrierName = subs.get(i).getCarrierName();
+            if (!TextUtils.isEmpty(carrierName) && simState == State.READY) {
+                ServiceState ss = mKeyguardUpdateMonitor.getServiceState(subId);
+                if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) {
+                    displayText = carrierName;
+                    break;
+                }
+            }
+        }
+
+        setText(displayText);
+    }
+}
diff --git a/com/android/systemui/statusbar/ViewTransformationHelper.java b/com/android/systemui/statusbar/ViewTransformationHelper.java
index 5353005..09b11c2 100644
--- a/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -35,7 +35,8 @@
 /**
  * A view that can be transformed to and from.
  */
-public class ViewTransformationHelper implements TransformableView {
+public class ViewTransformationHelper implements TransformableView,
+        TransformState.TransformInfo {
 
     private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
 
@@ -59,7 +60,7 @@
     public TransformState getCurrentState(int fadingView) {
         View view = mTransformedViews.get(fadingView);
         if (view != null && view.getVisibility() != View.GONE) {
-            return TransformState.createFrom(view);
+            return TransformState.createFrom(view, this);
         }
         return null;
     }
@@ -88,6 +89,7 @@
                         endRunnable.run();
                     }
                     setVisible(false);
+                    mViewTransformationAnimation = null;
                 } else {
                     abortTransformations();
                 }
@@ -245,7 +247,7 @@
     }
 
     public void resetTransformedView(View view) {
-        TransformState state = TransformState.createFrom(view);
+        TransformState state = TransformState.createFrom(view, this);
         state.setVisible(true /* visible */, true /* force */);
         state.recycle();
     }
@@ -257,6 +259,11 @@
         return new ArraySet<>(mTransformedViews.values());
     }
 
+    @Override
+    public boolean isAnimating() {
+        return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
+    }
+
     public static abstract class CustomTransformation {
         /**
          * Transform a state to the given view
diff --git a/com/android/systemui/statusbar/car/CarStatusBar.java b/com/android/systemui/statusbar/car/CarStatusBar.java
index fed2ebe..5941af2 100644
--- a/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -45,8 +45,9 @@
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.StatusBarState;
@@ -84,7 +85,7 @@
     public void start() {
         super.start();
         mTaskStackListener = new TaskStackListenerImpl();
-        SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         registerPackageChangeReceivers();
 
         mStackScroller.setScrollingEnabled(true);
@@ -305,14 +306,14 @@
     }
 
     /**
-     * An implementation of TaskStackChangeListener, that listens for changes in the system task
+     * An implementation of SysUiTaskStackChangeListener, that listens for changes in the system task
      * stack and notifies the navigation bar.
      */
-    private class TaskStackListenerImpl extends TaskStackChangeListener {
+    private class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
         @Override
         public void onTaskStackChanged() {
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
+            ActivityManager.RunningTaskInfo runningTaskInfo =
+                    ActivityManagerWrapper.getInstance().getRunningTask();
             if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
                 mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
                         runningTaskInfo);
diff --git a/com/android/systemui/statusbar/notification/ImageTransformState.java b/com/android/systemui/statusbar/notification/ImageTransformState.java
index 92f26d6..8227b77 100644
--- a/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -39,8 +39,8 @@
     private Icon mIcon;
 
     @Override
-    public void initFrom(View view) {
-        super.initFrom(view);
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
         if (view instanceof ImageView) {
             mIcon = (Icon) view.getTag(ICON_TAG);
         }
diff --git a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
new file mode 100644
index 0000000..fc420eb
--- /dev/null
+++ b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.content.res.Resources;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingLayout;
+import com.android.internal.widget.MessagingLinearLayout;
+import com.android.internal.widget.MessagingMessage;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A transform state of the action list
+*/
+public class MessagingLayoutTransformState extends TransformState {
+
+    private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool
+            = new Pools.SimplePool<>(40);
+    private MessagingLinearLayout mMessageContainer;
+    private MessagingLayout mMessagingLayout;
+    private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>();
+    private float mRelativeTranslationOffset;
+
+    public static MessagingLayoutTransformState obtain() {
+        MessagingLayoutTransformState instance = sInstancePool.acquire();
+        if (instance != null) {
+            return instance;
+        }
+        return new MessagingLayoutTransformState();
+    }
+
+    @Override
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
+        if (mTransformedView instanceof MessagingLinearLayout) {
+            mMessageContainer = (MessagingLinearLayout) mTransformedView;
+            mMessagingLayout = mMessageContainer.getMessagingLayout();
+            Resources resources = view.getContext().getResources();
+            mRelativeTranslationOffset = resources.getDisplayMetrics().density * 8;
+        }
+    }
+
+    @Override
+    public boolean transformViewTo(TransformState otherState, float transformationAmount) {
+        if (otherState instanceof MessagingLayoutTransformState) {
+            // It's a party! Let's transform between these two layouts!
+            transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+                    true /* to */);
+            return true;
+        } else {
+            return super.transformViewTo(otherState, transformationAmount);
+        }
+    }
+
+    @Override
+    public void transformViewFrom(TransformState otherState, float transformationAmount) {
+        if (otherState instanceof MessagingLayoutTransformState) {
+            // It's a party! Let's transform between these two layouts!
+            transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+                    false /* to */);
+        } else {
+            super.transformViewFrom(otherState, transformationAmount);
+        }
+    }
+
+    private void transformViewInternal(MessagingLayoutTransformState mlt,
+            float transformationAmount, boolean to) {
+        ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
+                mMessagingLayout.getMessagingGroups());
+        ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
+                mlt.mMessagingLayout.getMessagingGroups());
+        HashMap<MessagingGroup, MessagingGroup> pairs = findPairs(ownGroups, otherGroups);
+        MessagingGroup lastPairedGroup = null;
+        float currentTranslation = 0;
+        float transformationDistanceRemaining = 0;
+        for (int i = ownGroups.size() - 1; i >= 0; i--) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            MessagingGroup matchingGroup = pairs.get(ownGroup);
+            if (!isGone(ownGroup)) {
+                if (matchingGroup != null) {
+                    transformGroups(ownGroup, matchingGroup, transformationAmount, to);
+                    if (lastPairedGroup == null) {
+                        lastPairedGroup = ownGroup;
+                        if (to){
+                            float totalTranslation = ownGroup.getTop() - matchingGroup.getTop();
+                            transformationDistanceRemaining
+                                    = matchingGroup.getAvatar().getTranslationY();
+                            currentTranslation = transformationDistanceRemaining - totalTranslation;
+                        } else {
+                            float totalTranslation = matchingGroup.getTop() - ownGroup.getTop();
+                            currentTranslation = ownGroup.getAvatar().getTranslationY();
+                            transformationDistanceRemaining = currentTranslation - totalTranslation;
+                        }
+                    }
+                } else {
+                    float groupTransformationAmount = transformationAmount;
+                    if (lastPairedGroup != null) {
+                        adaptGroupAppear(ownGroup, transformationAmount, currentTranslation,
+                                to);
+                        int distance = lastPairedGroup.getTop() - ownGroup.getTop();
+                        float transformationDistance = mTransformInfo.isAnimating()
+                                ? distance
+                                : ownGroup.getHeight() * 0.75f;
+                        float translationProgress = transformationDistanceRemaining
+                                - (distance - transformationDistance);
+                        groupTransformationAmount =
+                                translationProgress / transformationDistance;
+                        groupTransformationAmount = Math.max(0.0f, Math.min(1.0f,
+                                groupTransformationAmount));
+                        if (to) {
+                            groupTransformationAmount = 1.0f - groupTransformationAmount;
+                        }
+                    }
+                    if (to) {
+                        disappear(ownGroup, groupTransformationAmount);
+                    } else {
+                        appear(ownGroup, groupTransformationAmount);
+                    }
+                }
+            }
+        }
+    }
+
+    private void appear(MessagingGroup ownGroup, float transformationAmount) {
+        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+        for (int j = 0; j < ownMessages.getChildCount(); j++) {
+            View child = ownMessages.getChildAt(j);
+            if (isGone(child)) {
+                continue;
+            }
+            appear(child, transformationAmount);
+            setClippingDeactivated(child, true);
+        }
+        appear(ownGroup.getAvatar(), transformationAmount);
+        appear(ownGroup.getSender(), transformationAmount);
+        setClippingDeactivated(ownGroup.getSender(), true);
+        setClippingDeactivated(ownGroup.getAvatar(), true);
+    }
+
+    private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount,
+            float overallTranslation, boolean to) {
+        float relativeOffset;
+        if (to) {
+            relativeOffset = transformationAmount * mRelativeTranslationOffset;
+        } else {
+            relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset;
+        }
+        if (ownGroup.getSender().getVisibility() != View.GONE) {
+            relativeOffset *= 0.5f;
+        }
+        ownGroup.getMessageContainer().setTranslationY(relativeOffset);
+        ownGroup.setTranslationY(overallTranslation * 0.85f);
+    }
+
+    private void disappear(MessagingGroup ownGroup, float transformationAmount) {
+        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+        for (int j = 0; j < ownMessages.getChildCount(); j++) {
+            View child = ownMessages.getChildAt(j);
+            if (isGone(child)) {
+                continue;
+            }
+            disappear(child, transformationAmount);
+            setClippingDeactivated(child, true);
+        }
+        disappear(ownGroup.getAvatar(), transformationAmount);
+        disappear(ownGroup.getSender(), transformationAmount);
+        setClippingDeactivated(ownGroup.getSender(), true);
+        setClippingDeactivated(ownGroup.getAvatar(), true);
+    }
+
+    private void appear(View child, float transformationAmount) {
+        if (child.getVisibility() == View.GONE) {
+            return;
+        }
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.appear(transformationAmount, null);
+        ownState.recycle();
+    }
+
+    private void disappear(View child, float transformationAmount) {
+        if (child.getVisibility() == View.GONE) {
+            return;
+        }
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.disappear(transformationAmount, null);
+        ownState.recycle();
+    }
+
+    private ArrayList<MessagingGroup> filterHiddenGroups(
+            ArrayList<MessagingGroup> groups) {
+        ArrayList<MessagingGroup> result = new ArrayList<>(groups);
+        for (int i = 0; i < result.size(); i++) {
+            MessagingGroup messagingGroup = result.get(i);
+            if (isGone(messagingGroup)) {
+                result.remove(i);
+                i--;
+            }
+        }
+        return result;
+    }
+
+    private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
+            float transformationAmount, boolean to) {
+        transformView(transformationAmount, to, ownGroup.getSender(), otherGroup.getSender(),
+                true /* sameAsAny */);
+        transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
+                true /* sameAsAny */);
+        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+        MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
+        float previousTranslation = 0;
+        for (int i = 0; i < ownMessages.getChildCount(); i++) {
+            View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
+            if (isGone(child)) {
+                continue;
+            }
+            int otherIndex = otherMessages.getChildCount() - 1 - i;
+            View otherChild = null;
+            if (otherIndex >= 0) {
+                otherChild = otherMessages.getChildAt(otherIndex);
+                if (isGone(otherChild)) {
+                    otherChild = null;
+                }
+            }
+            if (otherChild == null) {
+                float distanceToTop = child.getTop() + child.getHeight() + previousTranslation;
+                transformationAmount = distanceToTop / child.getHeight();
+                transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount));
+                if (to) {
+                    transformationAmount = 1.0f - transformationAmount;
+                }
+            }
+            transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
+            if (otherChild == null) {
+                child.setTranslationY(previousTranslation);
+                setClippingDeactivated(child, true);
+            } else if (to) {
+                float totalTranslation = child.getTop() + ownGroup.getTop()
+                        - otherChild.getTop() - otherChild.getTop();
+                previousTranslation = otherChild.getTranslationY() - totalTranslation;
+            } else {
+                previousTranslation = child.getTranslationY();
+            }
+        }
+    }
+
+    private void transformView(float transformationAmount, boolean to, View ownView,
+            View otherView, boolean sameAsAny) {
+        TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
+        if (!mTransformInfo.isAnimating()) {
+            ownState.setDefaultInterpolator(Interpolators.LINEAR);
+        }
+        ownState.setIsSameAsAnyView(sameAsAny);
+        if (to) {
+            if (otherView != null) {
+                TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+                ownState.transformViewTo(otherState, transformationAmount);
+                otherState.recycle();
+            } else {
+                ownState.disappear(transformationAmount, null);
+            }
+        } else {
+            if (otherView != null) {
+                TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+                ownState.transformViewFrom(otherState, transformationAmount);
+                otherState.recycle();
+            } else {
+                ownState.appear(transformationAmount, null);
+            }
+        }
+        ownState.recycle();
+    }
+
+    private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups,
+            ArrayList<MessagingGroup> otherGroups) {
+        mGroupMap.clear();
+        int lastMatch = Integer.MAX_VALUE;
+        for (int i = ownGroups.size() - 1; i >= 0; i--) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            MessagingGroup bestMatch = null;
+            int bestCompatibility = 0;
+            for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) {
+                MessagingGroup otherGroup = otherGroups.get(j);
+                int compatibility = ownGroup.calculateGroupCompatibility(otherGroup);
+                if (compatibility > bestCompatibility) {
+                    bestCompatibility = compatibility;
+                    bestMatch = otherGroup;
+                    lastMatch = j;
+                }
+            }
+            if (bestMatch != null) {
+                mGroupMap.put(ownGroup, bestMatch);
+            }
+        }
+        return mGroupMap;
+    }
+
+    private boolean isGone(View view) {
+        if (view.getVisibility() == View.GONE) {
+            return true;
+        }
+        final ViewGroup.LayoutParams lp = view.getLayoutParams();
+        if (lp instanceof MessagingLinearLayout.LayoutParams
+                && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void setVisible(boolean visible, boolean force) {
+        resetTransformedView();
+        ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+        for (int i = 0; i < ownGroups.size(); i++) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            if (!isGone(ownGroup)) {
+                MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+                for (int j = 0; j < ownMessages.getChildCount(); j++) {
+                    MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
+                    setVisible(child, visible, force);
+                }
+                setVisible(ownGroup.getAvatar(), visible, force);
+                setVisible(ownGroup.getSender(), visible, force);
+            }
+        }
+    }
+
+    private void setVisible(View child, boolean visible, boolean force) {
+        if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) {
+            return;
+        }
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.setVisible(visible, force);
+        ownState.recycle();
+    }
+
+    @Override
+    protected void resetTransformedView() {
+        super.resetTransformedView();
+        ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+        for (int i = 0; i < ownGroups.size(); i++) {
+            MessagingGroup ownGroup = ownGroups.get(i);
+            if (!isGone(ownGroup)) {
+                MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+                for (int j = 0; j < ownMessages.getChildCount(); j++) {
+                    View child = ownMessages.getChildAt(j);
+                    if (isGone(child)) {
+                        continue;
+                    }
+                    resetTransformedView(child);
+                    setClippingDeactivated(child, false);
+                }
+                resetTransformedView(ownGroup.getAvatar());
+                resetTransformedView(ownGroup.getSender());
+                setClippingDeactivated(ownGroup.getAvatar(), false);
+                setClippingDeactivated(ownGroup.getSender(), false);
+                ownGroup.setTranslationY(0);
+                ownGroup.getMessageContainer().setTranslationY(0);
+            }
+        }
+    }
+
+    @Override
+    public void prepareFadeIn() {
+        super.prepareFadeIn();
+        setVisible(true /* visible */, false /* force */);
+    }
+
+    private void resetTransformedView(View child) {
+        TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+        ownState.resetTransformedView();
+        ownState.recycle();
+    }
+
+    @Override
+    protected void reset() {
+        super.reset();
+        mMessageContainer = null;
+        mMessagingLayout = null;
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        mGroupMap.clear();;
+        sInstancePool.release(this);
+    }
+}
diff --git a/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index f6ee1ca..fb5644f 100644
--- a/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.notification;
 
+import com.android.internal.widget.MessagingLayout;
 import com.android.internal.widget.MessagingLinearLayout;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
 
@@ -32,41 +34,20 @@
  */
 public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
 
-    private View mContractedMessage;
-    private ArrayList<View> mHistoricMessages = new ArrayList<View>();
+    private final int mMinHeightWithActions;
+    private MessagingLayout mMessagingLayout;
+    private MessagingLinearLayout mMessagingLinearLayout;
 
     protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
         super(ctx, view, row);
+        mMessagingLayout = (MessagingLayout) view;
+        mMinHeightWithActions = NotificationUtils.getFontScaledHeight(ctx,
+                R.dimen.notification_messaging_actions_min_height);
     }
 
     private void resolveViews() {
-        mContractedMessage = null;
-
-        View container = mView.findViewById(com.android.internal.R.id.notification_messaging);
-        if (container instanceof MessagingLinearLayout
-                && ((MessagingLinearLayout) container).getChildCount() > 0) {
-            MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
-
-            int childCount = messagingContainer.getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                View child = messagingContainer.getChildAt(i);
-
-                if (child.getVisibility() == View.GONE
-                        && child instanceof TextView
-                        && !TextUtils.isEmpty(((TextView) child).getText())) {
-                    mHistoricMessages.add(child);
-                }
-
-                // Only consider the first visible child - transforming to a position other than the
-                // first looks bad because we have to move across other messages that are fading in.
-                if (child.getId() == messagingContainer.getContractedChildId()) {
-                    mContractedMessage = child;
-                } else if (child.getVisibility() == View.VISIBLE) {
-                    break;
-                }
-            }
-        }
+        mMessagingLinearLayout = mMessagingLayout.getMessagingLinearLayout();
     }
 
     @Override
@@ -81,16 +62,22 @@
     protected void updateTransformedTypes() {
         // This also clears the existing types
         super.updateTransformedTypes();
-        if (mContractedMessage != null) {
-            mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
-                    mContractedMessage);
+        if (mMessagingLinearLayout != null) {
+            mTransformationHelper.addTransformedView(mMessagingLinearLayout.getId(),
+                    mMessagingLinearLayout);
         }
     }
 
     @Override
     public void setRemoteInputVisible(boolean visible) {
-        for (int i = 0; i < mHistoricMessages.size(); i++) {
-            mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+        mMessagingLayout.showHistoricMessages(visible);
+    }
+
+    @Override
+    public int getMinLayoutHeight() {
+        if (mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE) {
+            return mMinHeightWithActions;
         }
+        return super.getMinLayoutHeight();
     }
 }
diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index bb979eb..fd085d9 100644
--- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -41,7 +41,7 @@
     private ProgressBar mProgressBar;
     private TextView mTitle;
     private TextView mText;
-    private View mActionsContainer;
+    protected View mActionsContainer;
     private View mReplyAction;
     private Rect mTmpRect = new Rect();
 
diff --git a/com/android/systemui/statusbar/notification/NotificationUtils.java b/com/android/systemui/statusbar/notification/NotificationUtils.java
index 3115361..af393c9 100644
--- a/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -66,4 +66,14 @@
                 Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
     }
 
+    /**
+     * @param dimenId the dimen to look up
+     * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
+     */
+    public static int getFontScaledHeight(Context context, int dimenId) {
+        int dimensionPixelSize = context.getResources().getDimensionPixelSize(dimenId);
+        float factor = Math.max(1.0f, context.getResources().getDisplayMetrics().scaledDensity /
+                context.getResources().getDisplayMetrics().density);
+        return (int) (dimensionPixelSize * factor);
+    }
 }
diff --git a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 5200d69..1cd5f15 100644
--- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,4 +190,8 @@
     public boolean disallowSingleClick(float x, float y) {
         return false;
     }
+
+    public int getMinLayoutHeight() {
+        return 0;
+    }
 }
diff --git a/com/android/systemui/statusbar/notification/TextViewTransformState.java b/com/android/systemui/statusbar/notification/TextViewTransformState.java
index c4aabe4..178c813 100644
--- a/com/android/systemui/statusbar/notification/TextViewTransformState.java
+++ b/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -33,8 +33,8 @@
     private TextView mText;
 
     @Override
-    public void initFrom(View view) {
-        super.initFrom(view);
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
         if (view instanceof TextView) {
             mText = (TextView) view;
         }
diff --git a/com/android/systemui/statusbar/notification/TransformState.java b/com/android/systemui/statusbar/notification/TransformState.java
index bafedff..ad07af0 100644
--- a/com/android/systemui/statusbar/notification/TransformState.java
+++ b/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,8 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
@@ -43,23 +45,46 @@
     public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
 
     private static final float UNDEFINED = -1f;
-    private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
-    private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
-    private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
     private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
     private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
     private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
     private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
     private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
+    private static ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS
+            = new ViewClippingUtil.ClippingParameters() {
+        @Override
+        public boolean shouldFinish(View view) {
+            if (view instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                return !row.isChildInGroup();
+            }
+            return false;
+        }
+
+        @Override
+        public void onClippingStateChanged(View view, boolean isClipping) {
+            if (view instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                if (isClipping) {
+                    row.setClipToActualHeight(true);
+                } else if (row.isChildInGroup()) {
+                    row.setClipToActualHeight(false);
+                }
+            }
+        }
+    };
 
     protected View mTransformedView;
+    protected TransformInfo mTransformInfo;
     private int[] mOwnPosition = new int[2];
     private boolean mSameAsAny;
     private float mTransformationEndY = UNDEFINED;
     private float mTransformationEndX = UNDEFINED;
+    private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
 
-    public void initFrom(View view) {
+    public void initFrom(View view, TransformInfo transformInfo) {
         mTransformedView = view;
+        mTransformInfo = transformInfo;
     }
 
     /**
@@ -108,13 +133,16 @@
         final View transformedView = mTransformedView;
         boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
         boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
-        boolean transformScale = transformScale(otherState);
+        boolean differentHeight = otherState.getViewHeight() != getViewHeight();
+        boolean differentWidth = otherState.getViewWidth() != getViewWidth();
+        boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth);
         // lets animate the positions correctly
         if (transformationAmount == 0.0f
                 || transformX && getTransformationStartX() == UNDEFINED
                 || transformY && getTransformationStartY() == UNDEFINED
-                || transformScale && getTransformationStartScaleX() == UNDEFINED
-                || transformScale && getTransformationStartScaleY() == UNDEFINED) {
+                || transformScale && getTransformationStartScaleX() == UNDEFINED && differentWidth
+                || transformScale && getTransformationStartScaleY() == UNDEFINED
+                        && differentHeight) {
             int[] otherPosition;
             if (transformationAmount != 0.0f) {
                 otherPosition = otherState.getLaidOutLocationOnScreen();
@@ -132,14 +160,14 @@
                 }
                 // we also want to animate the scale if we're the same
                 View otherView = otherState.getTransformedView();
-                if (transformScale && otherState.getViewWidth() != getViewWidth()) {
+                if (transformScale && differentWidth) {
                     setTransformationStartScaleX(otherState.getViewWidth() * otherView.getScaleX()
                             / (float) getViewWidth());
                     transformedView.setPivotX(0);
                 } else {
                     setTransformationStartScaleX(UNDEFINED);
                 }
-                if (transformScale && otherState.getViewHeight() != getViewHeight()) {
+                if (transformScale && differentHeight) {
                     setTransformationStartScaleY(otherState.getViewHeight() * otherView.getScaleY()
                             / (float) getViewHeight());
                     transformedView.setPivotY(0);
@@ -159,7 +187,7 @@
             }
             setClippingDeactivated(transformedView, true);
         }
-        float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+        float interpolatedValue = mDefaultInterpolator.getInterpolation(
                 transformationAmount);
         if (transformX) {
             float interpolation = interpolatedValue;
@@ -297,7 +325,7 @@
             }
             setClippingDeactivated(transformedView, true);
         }
-        float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+        float interpolatedValue = mDefaultInterpolator.getInterpolation(
                 transformationAmount);
         int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
         int[] ownPosition = getLaidOutLocationOnScreen();
@@ -354,59 +382,8 @@
         }
     }
 
-    public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
-        if (!(transformedView.getParent() instanceof ViewGroup)) {
-            return;
-        }
-        ViewGroup view = (ViewGroup) transformedView.getParent();
-        while (true) {
-            ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
-            if (clipSet == null) {
-                clipSet = new ArraySet<>();
-                view.setTag(CLIP_CLIPPING_SET, clipSet);
-            }
-            Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
-            if (clipChildren == null) {
-                clipChildren = view.getClipChildren();
-                view.setTag(CLIP_CHILDREN_TAG, clipChildren);
-            }
-            Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
-            if (clipToPadding == null) {
-                clipToPadding = view.getClipToPadding();
-                view.setTag(CLIP_TO_PADDING, clipToPadding);
-            }
-            ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
-                    ? (ExpandableNotificationRow) view
-                    : null;
-            if (!deactivated) {
-                clipSet.remove(transformedView);
-                if (clipSet.isEmpty()) {
-                    view.setClipChildren(clipChildren);
-                    view.setClipToPadding(clipToPadding);
-                    view.setTag(CLIP_CLIPPING_SET, null);
-                    if (row != null) {
-                        row.setClipToActualHeight(true);
-                    }
-                }
-            } else {
-                clipSet.add(transformedView);
-                view.setClipChildren(false);
-                view.setClipToPadding(false);
-                if (row != null && row.isChildInGroup()) {
-                    // We still want to clip to the parent's height
-                    row.setClipToActualHeight(false);
-                }
-            }
-            if (row != null && !row.isChildInGroup()) {
-                return;
-            }
-            final ViewParent parent = view.getParent();
-            if (parent instanceof ViewGroup) {
-                view = (ViewGroup) parent;
-            } else {
-                return;
-            }
-        }
+    protected void setClippingDeactivated(final View transformedView, boolean deactivated) {
+        ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, CLIPPING_PARAMETERS);
     }
 
     public int[] getLaidOutLocationOnScreen() {
@@ -423,6 +400,9 @@
         // remove scale
         mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX();
         mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
+
+        // Remove local translations
+        mOwnPosition[1] -= MessagingPropertyAnimator.getLocalTranslationY(mTransformedView);
         return mOwnPosition;
     }
 
@@ -444,20 +424,26 @@
         CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
     }
 
-    public static TransformState createFrom(View view) {
+    public static TransformState createFrom(View view,
+            TransformInfo transformInfo) {
         if (view instanceof TextView) {
             TextViewTransformState result = TextViewTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
             return result;
         }
         if (view.getId() == com.android.internal.R.id.actions_container) {
             ActionListTransformState result = ActionListTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
+            return result;
+        }
+        if (view.getId() == com.android.internal.R.id.notification_messaging) {
+            MessagingLayoutTransformState result = MessagingLayoutTransformState.obtain();
+            result.initFrom(view, transformInfo);
             return result;
         }
         if (view instanceof ImageView) {
             ImageTransformState result = ImageTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
             if (view.getId() == com.android.internal.R.id.reply_icon_action) {
                 ((TransformState) result).setIsSameAsAnyView(true);
             }
@@ -465,15 +451,15 @@
         }
         if (view instanceof ProgressBar) {
             ProgressTransformState result = ProgressTransformState.obtain();
-            result.initFrom(view);
+            result.initFrom(view, transformInfo);
             return result;
         }
         TransformState result = obtain();
-        result.initFrom(view);
+        result.initFrom(view, transformInfo);
         return result;
     }
 
-    private void setIsSameAsAnyView(boolean sameAsAny) {
+    public void setIsSameAsAnyView(boolean sameAsAny) {
         mSameAsAny = sameAsAny;
     }
 
@@ -533,6 +519,7 @@
         mSameAsAny = false;
         mTransformationEndX = UNDEFINED;
         mTransformationEndY = UNDEFINED;
+        mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
     }
 
     public void setVisible(boolean visible, boolean force) {
@@ -578,4 +565,12 @@
     public View getTransformedView() {
         return mTransformedView;
     }
+
+    public void setDefaultInterpolator(Interpolator interpolator) {
+        mDefaultInterpolator = interpolator;
+    }
+
+    public interface TransformInfo {
+        boolean isAnimating();
+    }
 }
diff --git a/com/android/systemui/statusbar/phone/AutoTileManager.java b/com/android/systemui/statusbar/phone/AutoTileManager.java
index 1bd90fa..149ec0b 100644
--- a/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -20,7 +20,7 @@
 import android.provider.Settings.Secure;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
 import com.android.systemui.Dependency;
 import com.android.systemui.Prefs;
 import com.android.systemui.Prefs.Key;
@@ -78,8 +78,8 @@
         }
 
         if (!mAutoTracker.isAdded(NIGHT)
-                && NightDisplayController.isAvailable(mContext)) {
-            Dependency.get(NightDisplayController.class).setListener(mNightDisplayCallback);
+                && ColorDisplayController.isAvailable(mContext)) {
+            Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback);
         }
     }
 
@@ -89,7 +89,7 @@
         Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
         Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
         Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
-        Dependency.get(NightDisplayController.class).setListener(null);
+        Dependency.get(ColorDisplayController.class).setListener(null);
     }
 
     private final ManagedProfileController.Callback mProfileCallback =
@@ -139,8 +139,8 @@
     };
 
     @VisibleForTesting
-    final NightDisplayController.Callback mNightDisplayCallback =
-            new NightDisplayController.Callback() {
+    final ColorDisplayController.Callback mColorDisplayCallback =
+            new ColorDisplayController.Callback() {
         @Override
         public void onActivated(boolean activated) {
             if (activated) {
@@ -150,8 +150,8 @@
 
         @Override
         public void onAutoModeChanged(int autoMode) {
-            if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM
-                    || autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+            if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM
+                    || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
                 addNightTile();
             }
         }
@@ -160,7 +160,7 @@
             if (mAutoTracker.isAdded(NIGHT)) return;
             mHost.addTile(NIGHT);
             mAutoTracker.setTileAdded(NIGHT);
-            mHandler.post(() -> Dependency.get(NightDisplayController.class)
+            mHandler.post(() -> Dependency.get(ColorDisplayController.class)
                     .setListener(null));
         }
     };
diff --git a/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 2c3f452..61f3130 100644
--- a/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -60,6 +60,7 @@
     private StatusBar mStatusBarComponent;
     private DarkIconManager mDarkIconManager;
     private SignalClusterView mSignalClusterView;
+    private View mOperatorNameFrame;
 
     private SignalCallback mSignalCallback = new SignalCallback() {
         @Override
@@ -97,6 +98,7 @@
         // Default to showing until we know otherwise.
         showSystemIconArea(false);
         initEmergencyCryptkeeperText();
+        initOperatorName();
     }
 
     @Override
@@ -150,8 +152,10 @@
         if ((diff1 & DISABLE_SYSTEM_INFO) != 0) {
             if ((state1 & DISABLE_SYSTEM_INFO) != 0) {
                 hideSystemIconArea(animate);
+                hideOperatorName(animate);
             } else {
                 showSystemIconArea(animate);
+                showOperatorName(animate);
             }
         }
         if ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0) {
@@ -207,6 +211,18 @@
         animateShow(mNotificationIconAreaInner, animate);
     }
 
+    public void hideOperatorName(boolean animate) {
+        if (mOperatorNameFrame != null) {
+            animateHide(mOperatorNameFrame, animate);
+        }
+    }
+
+    public void showOperatorName(boolean animate) {
+        if (mOperatorNameFrame != null) {
+            animateShow(mOperatorNameFrame, animate);
+        }
+    }
+
     /**
      * Hides a view.
      */
@@ -268,4 +284,11 @@
             parent.removeView(emergencyViewStub);
         }
     }
+
+    private void initOperatorName() {
+        if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) {
+            ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
+            mOperatorNameFrame = stub.inflate();
+        }
+    }
 }
diff --git a/com/android/systemui/statusbar/phone/LightBarController.java b/com/android/systemui/statusbar/phone/LightBarController.java
index d226fed..1c361ca 100644
--- a/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/com/android/systemui/statusbar/phone/LightBarController.java
@@ -175,9 +175,8 @@
     private boolean isLight(int vis, int barMode, int flag) {
         boolean isTransparentBar = (barMode == MODE_TRANSPARENT
                 || barMode == MODE_LIGHTS_OUT_TRANSPARENT);
-        boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
         boolean light = (vis & flag) != 0;
-        return allowLight && light;
+        return isTransparentBar && light;
     }
 
     private boolean animateChange() {
diff --git a/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index ee9a791..bed6d82 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -19,20 +19,24 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
 import com.android.systemui.Dependency;
+import com.android.systemui.OverviewProxyService;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
+import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.tuner.TunerService;
 
@@ -46,6 +50,7 @@
 public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener
         implements TunerService.Tunable, GestureHelper {
 
+    private static final String TAG = "NavBarGestureHelper";
     private static final String KEY_DOCK_WINDOW_GESTURE = "overview_nav_bar_gesture";
     /**
      * When dragging from the navigation bar, we drag in recents.
@@ -72,10 +77,13 @@
     private final GestureDetector mTaskSwitcherDetector;
     private final int mScrollTouchSlop;
     private final int mMinFlingVelocity;
+    private final Matrix mTransformGlobalMatrix = new Matrix();
+    private final Matrix mTransformLocalMatrix = new Matrix();
     private int mTouchDownX;
     private int mTouchDownY;
     private boolean mDownOnRecents;
     private VelocityTracker mVelocityTracker;
+    private OverviewProxyService mOverviewEventSender = Dependency.get(OverviewProxyService.class);
 
     private boolean mDockWindowEnabled;
     private boolean mDockWindowTouchSlopExceeded;
@@ -107,15 +115,34 @@
         mIsRTL = isRTL;
     }
 
+    private boolean proxyMotionEvents(MotionEvent event) {
+        final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
+        if (overviewProxy != null) {
+            mNavigationBarView.requestUnbufferedDispatch(event);
+            event.transform(mTransformGlobalMatrix);
+            try {
+                overviewProxy.onMotionEvent(event);
+                return true;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Callback failed", e);
+            } finally {
+                event.transform(mTransformLocalMatrix);
+            }
+        }
+        return false;
+    }
+
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        // If we move more than a fixed amount, then start capturing for the
-        // task switcher detector
-        mTaskSwitcherDetector.onTouchEvent(event);
         int action = event.getAction();
+        boolean result = false;
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 mTouchDownX = (int) event.getX();
                 mTouchDownY = (int) event.getY();
+                mTransformGlobalMatrix.set(Matrix.IDENTITY_MATRIX);
+                mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX);
+                mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix);
+                mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
@@ -123,11 +150,10 @@
                 int y = (int) event.getY();
                 int xDiff = Math.abs(x - mTouchDownX);
                 int yDiff = Math.abs(y - mTouchDownY);
-                boolean exceededTouchSlop = !mIsVertical
-                        ? xDiff > mScrollTouchSlop && xDiff > yDiff
-                        : yDiff > mScrollTouchSlop && yDiff > xDiff;
+                boolean exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff
+                        || yDiff > mScrollTouchSlop && yDiff > xDiff;
                 if (exceededTouchSlop) {
-                    return true;
+                    result = true;
                 }
                 break;
             }
@@ -135,7 +161,12 @@
             case MotionEvent.ACTION_UP:
                 break;
         }
-        return mDockWindowEnabled && interceptDockWindowEvent(event);
+        if (!proxyMotionEvents(event)) {
+            // If we move more than a fixed amount, then start capturing for the
+            // task switcher detector, disabled when proxying motion events to launcher service
+            mTaskSwitcherDetector.onTouchEvent(event);
+        }
+        return result || (mDockWindowEnabled && interceptDockWindowEvent(event));
     }
 
     private boolean interceptDockWindowEvent(MotionEvent event) {
@@ -206,7 +237,7 @@
                     && mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) {
                 Rect initialBounds = null;
                 int dragMode = calculateDragMode();
-                int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+                int createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
                 if (dragMode == DRAG_MODE_DIVIDER) {
                     initialBounds = new Rect();
                     mDivider.getView().calculateBoundsForPosition(mIsVertical
@@ -218,10 +249,10 @@
                             initialBounds);
                 } else if (dragMode == DRAG_MODE_RECENTS && mTouchDownX
                         < mContext.getResources().getDisplayMetrics().widthPixels / 2) {
-                    createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+                    createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
                 }
-                boolean docked = mRecentsComponent.dockTopTask(dragMode, createMode, initialBounds,
-                        MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
+                boolean docked = mRecentsComponent.splitPrimaryTask(dragMode, createMode,
+                        initialBounds, MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
                 if (docked) {
                     mDragMode = dragMode;
                     if (mDragMode == DRAG_MODE_DIVIDER) {
@@ -275,7 +306,7 @@
     }
 
     public boolean onTouchEvent(MotionEvent event) {
-        boolean result = mTaskSwitcherDetector.onTouchEvent(event);
+        boolean result = proxyMotionEvents(event) || mTaskSwitcherDetector.onTouchEvent(event);
         if (mDockWindowEnabled) {
             result |= handleDockWindowEvent(event);
         }
diff --git a/com/android/systemui/statusbar/phone/NavigationBarView.java b/com/android/systemui/statusbar/phone/NavigationBarView.java
index 094129c..4e7f205 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -47,6 +47,8 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.DockedStackExistsListener;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.plugins.PluginListener;
@@ -196,6 +198,9 @@
         }
     }
 
+    private final OverviewProxyListener mOverviewProxyListener =
+            isConnected -> setSlippery(!isConnected);
+
     public NavigationBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -531,6 +536,24 @@
         }
     }
 
+    private void setSlippery(boolean slippery) {
+        boolean changed = false;
+        final ViewGroup navbarView = ((ViewGroup) getParent());
+        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView
+                .getLayoutParams();
+        if (slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) == 0) {
+            lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
+            changed = true;
+        } else if (!slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0) {
+            lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
+            changed = true;
+        }
+        if (changed) {
+            WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
+            wm.updateViewLayout(navbarView, lp);
+        }
+    }
+
     public void setMenuVisibility(final boolean show) {
         setMenuVisibility(show, false);
     }
@@ -756,6 +779,7 @@
         onPluginDisconnected(null); // Create default gesture helper
         Dependency.get(PluginManager.class).addPluginListener(this,
                 NavGesture.class, false /* Only one */);
+        Dependency.get(OverviewProxyService.class).addCallback(mOverviewProxyListener);
     }
 
     @Override
@@ -765,6 +789,7 @@
         if (mGestureHelper != null) {
             mGestureHelper.destroy();
         }
+        Dependency.get(OverviewProxyService.class).removeCallback(mOverviewProxyListener);
     }
 
     @Override
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index af03440..86a8f41 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -596,7 +596,7 @@
             mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
         }
         closeQs();
-        mStatusBar.closeAndSaveGuts(true /* leavebehind */, true /* force */,
+        mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
                 true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
         mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
                 true /* cancelAnimators */);
diff --git a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index b876286..09fe579 100644
--- a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -65,8 +65,9 @@
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.BluetoothController;
@@ -249,7 +250,7 @@
         mLocationController.addCallback(this);
 
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
-        SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskListener);
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskListener);
 
         // Clear out all old notifications on startup (only present in the case where sysui dies)
         NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
@@ -768,7 +769,7 @@
         mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
     }
 
-    private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
+    private final SysUiTaskStackChangeListener mTaskListener = new SysUiTaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
             // Listen for changes to stacks and then check which instant apps are foreground.
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index 9f03954..6775615 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -16,14 +16,16 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
-
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
 import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -39,10 +41,8 @@
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.INotificationManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
-import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.RemoteInput;
@@ -79,10 +79,7 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -114,14 +111,12 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.Display;
-import android.view.HapticFeedbackConstants;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.ThreadedRenderer;
 import android.view.View;
-import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.ViewTreeObserver;
@@ -145,6 +140,8 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingMessage;
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -157,6 +154,7 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.Interpolators;
+import com.android.systemui.OverviewProxyService;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
@@ -177,7 +175,6 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanel;
@@ -203,10 +200,11 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationGutsManager;
 import com.android.systemui.statusbar.NotificationInfo;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
@@ -239,7 +237,6 @@
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
         .OnChildLocationsChangedListener;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
 import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.volume.VolumeComponent;
@@ -251,10 +248,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.Stack;
 
 public class StatusBar extends SystemUI implements DemoMode,
@@ -263,7 +258,7 @@
         ActivatableNotificationView.OnActivatedListener,
         ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
         ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
-        ColorExtractor.OnColorsChangedListener, ConfigurationListener {
+        ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
     public static final boolean MULTIUSER_DEBUG = false;
 
     public static final boolean ENABLE_REMOTE_INPUT =
@@ -271,7 +266,7 @@
     public static final boolean ENABLE_CHILD_NOTIFICATIONS
             = SystemProperties.getBoolean("debug.child_notifs", true);
     public static final boolean FORCE_REMOTE_INPUT_HISTORY =
-            SystemProperties.getBoolean("debug.force_remoteinput_history", false);
+            SystemProperties.getBoolean("debug.force_remoteinput_history", true);
     private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
 
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -283,9 +278,6 @@
     protected static final boolean ENABLE_HEADS_UP = true;
     protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
-    // Must match constant in Settings. Used to highlight preferences when linking to Settings.
-    private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
-
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
 
     // Should match the values in PhoneWindowManager
@@ -304,7 +296,6 @@
     public static final boolean SPEW = false;
     public static final boolean DUMPTRUCK = true; // extra dumpsys info
     public static final boolean DEBUG_GESTURES = false;
-    public static final boolean DEBUG_MEDIA = false;
     public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
     public static final boolean DEBUG_CAMERA_LIFT = false;
 
@@ -454,6 +445,8 @@
     private final int[] mAbsPos = new int[2];
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
+    private NotificationGutsManager mGutsManager;
+
     // for disabling the status bar
     private int mDisabled1 = 0;
     private int mDisabled2 = 0;
@@ -549,31 +542,7 @@
     protected final PorterDuffXfermode mSrcOverXferMode =
             new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
 
-    private MediaSessionManager mMediaSessionManager;
-    private MediaController mMediaController;
-    private String mMediaNotificationKey;
-    private MediaMetadata mMediaMetadata;
-    private final MediaController.Callback mMediaListener = new MediaController.Callback() {
-        @Override
-        public void onPlaybackStateChanged(PlaybackState state) {
-            super.onPlaybackStateChanged(state);
-            if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
-            if (state != null) {
-                if (!isPlaybackActive(state.getState())) {
-                    clearCurrentMediaNotification();
-                    updateMediaMetaData(true, true);
-                }
-            }
-        }
-
-        @Override
-        public void onMetadataChanged(MediaMetadata metadata) {
-            super.onMetadataChanged(metadata);
-            if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
-            mMediaMetadata = metadata;
-            updateMediaMetaData(true, true);
-        }
-    };
+    private NotificationMediaManager mMediaManager;
 
     /** Keys of notifications currently visible to the user. */
     private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
@@ -696,7 +665,6 @@
     private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
-    private String mKeyToRemoveOnGutsClosed;
     private SysuiColorExtractor mColorExtractor;
     private ForegroundServiceController mForegroundServiceController;
     private ScreenLifecycle mScreenLifecycle;
@@ -813,6 +781,8 @@
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mLockPatternUtils = new LockPatternUtils(mContext);
 
+        mMediaManager = new NotificationMediaManager(this, mContext);
+
         // Connect in to the status bar manager service
         mCommandQueue = getComponent(CommandQueue.class);
         mCommandQueue.addCallbacks(this);
@@ -874,6 +844,7 @@
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_PRESENT);
+        filter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiver(mBaseBroadcastReceiver, filter);
 
         IntentFilter internalFilter = new IntentFilter();
@@ -898,16 +869,8 @@
             Slog.e(TAG, "Failed to register VR mode state listener: " + e);
         }
 
-        mNonBlockablePkgs = new HashSet<>();
-        Collections.addAll(mNonBlockablePkgs, res.getStringArray(
-                com.android.internal.R.array.config_nonBlockableNotificationPackages));
         // end old BaseStatusBar.start().
 
-        mMediaSessionManager
-                = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
-        // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
-        // in session state
-
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
         mSettingsObserver.onChange(false); // set up
@@ -955,6 +918,8 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+        mGutsManager = new NotificationGutsManager(this, mStackScroller,
+                mCheckSaveListener, mContext);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -1229,6 +1194,8 @@
     }
 
     public void onDensityOrFontScaleChanged() {
+        MessagingMessage.dropCache();
+        MessagingGroup.dropCache();
         // start old BaseStatusBar.onDensityOrFontScaleChanged().
         if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
             updateNotificationsOnDensityOrFontScaleChanged();
@@ -1295,12 +1262,12 @@
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
         for (int i = 0; i < activeNotifications.size(); i++) {
             Entry entry = activeNotifications.get(i);
-            boolean exposedGuts = mNotificationGutsExposed != null
-                    && entry.row.getGuts() == mNotificationGutsExposed;
+            boolean exposedGuts = mGutsManager.getExposedGuts() != null
+                    && entry.row.getGuts() == mGutsManager.getExposedGuts();
             entry.row.onDensityOrFontScaleChanged();
             if (exposedGuts) {
-                mNotificationGutsExposed = entry.row.getGuts();
-                bindGuts(entry.row, mGutsMenuItem);
+                mGutsManager.setExposedGuts(entry.row.getGuts());
+                mGutsManager.bindGuts(entry.row);
             }
         }
     }
@@ -1538,8 +1505,8 @@
         }
         int dockSide = WindowManagerProxy.getInstance().getDockSide();
         if (dockSide == WindowManager.DOCKED_INVALID) {
-            return mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
-                    ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
+            return mRecents.splitPrimaryTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
+                    ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
         } else {
             Divider divider = getComponent(Divider.class);
             if (divider != null && divider.isMinimized() && !divider.isHomeStackResizable()) {
@@ -1665,6 +1632,7 @@
         updateNotifications();
     }
 
+    @Override
     public void removeNotification(String key, RankingMap ranking) {
         boolean deferRemoval = false;
         abortExistingInflation(key);
@@ -1678,12 +1646,11 @@
                     || !mVisualStabilityManager.isReorderingAllowed();
             deferRemoval = !mHeadsUpManager.removeNotification(key,  ignoreEarliestRemovalTime);
         }
-        if (key.equals(mMediaNotificationKey)) {
-            clearCurrentMediaNotification();
-            updateMediaMetaData(true, true);
-        }
-        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
-            Entry entry = mNotificationData.get(key);
+        mMediaManager.onNotificationRemoved(key);
+
+        Entry entry = mNotificationData.get(key);
+        if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
+                && entry.row != null && !entry.row.isDismissed()) {
             StatusBarNotification sbn = entry.notification;
 
             Notification.Builder b = Notification.Builder
@@ -1719,6 +1686,7 @@
                 deferRemoval = false;
             }
             if (updated) {
+                Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
                 mKeysKeptForRemoteInput.add(entry.key);
                 return;
             }
@@ -1728,7 +1696,6 @@
             mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
             return;
         }
-        Entry entry = mNotificationData.get(key);
 
         if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
                 && (entry.row != null && !entry.row.isDismissed())) {
@@ -1736,12 +1703,12 @@
             mRemoteInputEntriesToRemoveOnCollapse.add(entry);
             return;
         }
-        if (entry != null && mNotificationGutsExposed != null
-                && mNotificationGutsExposed == entry.row.getGuts() && entry.row.getGuts() != null
-                && !entry.row.getGuts().isLeavebehind()) {
+        if (entry != null && mGutsManager.getExposedGuts() != null
+                && mGutsManager.getExposedGuts() == entry.row.getGuts()
+                && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
             Log.w(TAG, "Keeping notification because it's showing guts. " + key);
             mLatestRankingMap = ranking;
-            mKeyToRemoveOnGutsClosed = key;
+            mGutsManager.setKeyToRemoveOnGutsClosed(key);
             return;
         }
 
@@ -2134,7 +2101,8 @@
         return entry.row.getParent() instanceof NotificationStackScrollLayout;
     }
 
-    protected void updateNotifications() {
+    @Override
+    public void updateNotifications() {
         mNotificationData.filterAndSort();
 
         updateNotificationShade();
@@ -2176,136 +2144,9 @@
             }
         }
 
-        findAndUpdateMediaNotifications();
+        mMediaManager.findAndUpdateMediaNotifications();
     }
 
-    public void findAndUpdateMediaNotifications() {
-        boolean metaDataChanged = false;
-
-        synchronized (mNotificationData) {
-            ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
-            final int N = activeNotifications.size();
-
-            // Promote the media notification with a controller in 'playing' state, if any.
-            Entry mediaNotification = null;
-            MediaController controller = null;
-            for (int i = 0; i < N; i++) {
-                final Entry entry = activeNotifications.get(i);
-
-                if (isMediaNotification(entry)) {
-                    final MediaSession.Token token =
-                            entry.notification.getNotification().extras.getParcelable(
-                                    Notification.EXTRA_MEDIA_SESSION);
-                    if (token != null) {
-                        MediaController aController = new MediaController(mContext, token);
-                        if (PlaybackState.STATE_PLAYING ==
-                                getMediaControllerPlaybackState(aController)) {
-                            if (DEBUG_MEDIA) {
-                                Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
-                                        + entry.notification.getKey());
-                            }
-                            mediaNotification = entry;
-                            controller = aController;
-                            break;
-                        }
-                    }
-                }
-            }
-            if (mediaNotification == null) {
-                // Still nothing? OK, let's just look for live media sessions and see if they match
-                // one of our notifications. This will catch apps that aren't (yet!) using media
-                // notifications.
-
-                if (mMediaSessionManager != null) {
-                    final List<MediaController> sessions
-                            = mMediaSessionManager.getActiveSessionsForUser(
-                                    null,
-                                    UserHandle.USER_ALL);
-
-                    for (MediaController aController : sessions) {
-                        if (PlaybackState.STATE_PLAYING ==
-                                getMediaControllerPlaybackState(aController)) {
-                            // now to see if we have one like this
-                            final String pkg = aController.getPackageName();
-
-                            for (int i = 0; i < N; i++) {
-                                final Entry entry = activeNotifications.get(i);
-                                if (entry.notification.getPackageName().equals(pkg)) {
-                                    if (DEBUG_MEDIA) {
-                                        Log.v(TAG, "DEBUG_MEDIA: found controller matching "
-                                                + entry.notification.getKey());
-                                    }
-                                    controller = aController;
-                                    mediaNotification = entry;
-                                    break;
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (controller != null && !sameSessions(mMediaController, controller)) {
-                // We have a new media session
-                clearCurrentMediaNotification();
-                mMediaController = controller;
-                mMediaController.registerCallback(mMediaListener);
-                mMediaMetadata = mMediaController.getMetadata();
-                if (DEBUG_MEDIA) {
-                    Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
-                            + mMediaMetadata);
-                }
-
-                if (mediaNotification != null) {
-                    mMediaNotificationKey = mediaNotification.notification.getKey();
-                    if (DEBUG_MEDIA) {
-                        Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
-                                + mMediaNotificationKey + " controller=" + mMediaController);
-                    }
-                }
-                metaDataChanged = true;
-            }
-        }
-
-        if (metaDataChanged) {
-            updateNotifications();
-        }
-        updateMediaMetaData(metaDataChanged, true);
-    }
-
-    private int getMediaControllerPlaybackState(MediaController controller) {
-        if (controller != null) {
-            final PlaybackState playbackState = controller.getPlaybackState();
-            if (playbackState != null) {
-                return playbackState.getState();
-            }
-        }
-        return PlaybackState.STATE_NONE;
-    }
-
-    private boolean isPlaybackActive(int state) {
-        return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
-                && state != PlaybackState.STATE_NONE;
-    }
-
-    private void clearCurrentMediaNotification() {
-        mMediaNotificationKey = null;
-        mMediaMetadata = null;
-        if (mMediaController != null) {
-            if (DEBUG_MEDIA) {
-                Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
-                        + mMediaController.getPackageName());
-            }
-            mMediaController.unregisterCallback(mMediaListener);
-        }
-        mMediaController = null;
-    }
-
-    private boolean sameSessions(MediaController a, MediaController b) {
-        if (a == b) return true;
-        if (a == null) return false;
-        return a.controlsSameSession(b);
-    }
 
     /**
      * Hide the album artwork that is fading out and release its bitmap.
@@ -2322,9 +2163,11 @@
         }
     };
 
+    // TODO: Move this to NotificationMediaManager.
     /**
      * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
      */
+    @Override
     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
         Trace.beginSection("StatusBar#updateMediaMetaData");
         if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
@@ -2343,19 +2186,22 @@
             return;
         }
 
+        MediaMetadata mediaMetadata = mMediaManager.getMediaMetadata();
+
         if (DEBUG_MEDIA) {
-            Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + mMediaNotificationKey
-                    + " metadata=" + mMediaMetadata
+            Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
+                    + mMediaManager.getMediaNotificationKey()
+                    + " metadata=" + mediaMetadata
                     + " metaDataChanged=" + metaDataChanged
                     + " state=" + mState);
         }
 
         Drawable artworkDrawable = null;
-        if (mMediaMetadata != null) {
+        if (mediaMetadata != null) {
             Bitmap artworkBitmap = null;
-            artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+            artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
             if (artworkBitmap == null) {
-                artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+                artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
                 // might still be null
             }
             if (artworkBitmap != null) {
@@ -2628,7 +2474,7 @@
 
     @Override  // NotificationData.Environment
     public String getCurrentMediaNotificationKey() {
-        return mMediaNotificationKey;
+        return mMediaManager.getMediaNotificationKey();
     }
 
     public boolean isScrimSrcModeEnabled() {
@@ -3093,8 +2939,8 @@
         mStatusBarWindowManager.setForceStatusBarVisible(false);
 
         // Close any guts that might be visible
-        closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */,
-                -1 /* x */, -1 /* y */, true /* resetMenu */);
+        mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+                true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
 
         runPostCollapseRunnables();
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
@@ -3275,12 +3121,8 @@
     }
 
     void checkBarMode(int mode, int windowState, BarTransitions transitions) {
-        final boolean powerSave = mBatteryController.isPowerSave();
         final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
-                && windowState != WINDOW_STATE_HIDDEN && !powerSave;
-        if (powerSave && getBarState() == StatusBarState.SHADE) {
-            mode = MODE_WARNING;
-        }
+                && windowState != WINDOW_STATE_HIDDEN;
         transitions.transitionTo(mode, anim);
     }
 
@@ -3445,28 +3287,18 @@
         pw.println(Settings.Global.zenModeToString(mZenMode));
         pw.print("  mUseHeadsUp=");
         pw.println(mUseHeadsUp);
-        pw.print("  mKeyToRemoveOnGutsClosed=");
-        pw.println(mKeyToRemoveOnGutsClosed);
+        pw.print("  mGutsManager: ");
+        if (mGutsManager != null) {
+            mGutsManager.dump(fd, pw, args);
+        }
         if (mStatusBarView != null) {
             dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
         }
 
-        pw.print("  mMediaSessionManager=");
-        pw.println(mMediaSessionManager);
-        pw.print("  mMediaNotificationKey=");
-        pw.println(mMediaNotificationKey);
-        pw.print("  mMediaController=");
-        pw.print(mMediaController);
-        if (mMediaController != null) {
-            pw.print(" state=" + mMediaController.getPlaybackState());
+        pw.println("  mMediaManager: ");
+        if (mMediaManager != null) {
+            mMediaManager.dump(fd, pw, args);
         }
-        pw.println();
-        pw.print("  mMediaMetadata=");
-        pw.print(mMediaMetadata);
-        if (mMediaMetadata != null) {
-            pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
-        }
-        pw.println();
 
         pw.println("  Panels: ");
         if (mNotificationPanel != null) {
@@ -3788,7 +3620,7 @@
             mReinflateNotificationsOnUserSwitched = false;
         }
         updateNotificationShade();
-        clearCurrentMediaNotification();
+        mMediaManager.clearCurrentMediaNotification();
         setLockscreenUser(newUserId);
     }
 
@@ -3868,10 +3700,10 @@
             if (visibleToUser) {
                 boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
                 boolean clearNotificationEffects =
-                        !isPanelFullyCollapsed() &&
+                        !isPresenterFullyCollapsed() &&
                         (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
                 int notificationLoad = mNotificationData.getActiveNotifications().size();
-                if (pinnedHeadsUp && isPanelFullyCollapsed())  {
+                if (pinnedHeadsUp && isPresenterFullyCollapsed())  {
                     notificationLoad = 1;
                 }
                 mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
@@ -4128,6 +3960,9 @@
                 }
             }
         }
+        if (modeChange || command.equals(COMMAND_OPERATOR)) {
+            dispatchDemoCommandToView(command, args, R.id.operator_name);
+        }
     }
 
     private void dispatchDemoCommandToView(String command, Bundle args, int id) {
@@ -4145,7 +3980,8 @@
         return mState;
     }
 
-    public boolean isPanelFullyCollapsed() {
+    @Override
+    public boolean isPresenterFullyCollapsed() {
         return mNotificationPanel.isFullyCollapsed();
     }
 
@@ -4515,12 +4351,14 @@
         final boolean useDarkTheme = systemColors != null
                 && (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
         if (isUsingDarkTheme() != useDarkTheme) {
-            try {
-                mOverlayManager.setEnabled("com.android.systemui.theme.dark",
-                        useDarkTheme, mCurrentUserId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Can't change theme", e);
-            }
+            mUiOffloadThread.submit(() -> {
+                try {
+                    mOverlayManager.setEnabled("com.android.systemui.theme.dark",
+                            useDarkTheme, mCurrentUserId);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Can't change theme", e);
+                }
+            });
         }
 
         // Lock wallpaper defines the color of the majority of the views, hence we'll use it
@@ -4726,7 +4564,7 @@
 
     public void onClosingFinished() {
         runPostCollapseRunnables();
-        if (!isPanelFullyCollapsed()) {
+        if (!isPresenterFullyCollapsed()) {
             // if we set it not to be focusable when collapsing, we have to undo it when we aborted
             // the closing
             mStatusBarWindowManager.setStatusBarFocusable(true);
@@ -5260,7 +5098,8 @@
     boolean isCameraAllowedByAdmin() {
         if (mDevicePolicyManager.getCameraDisabled(null, mCurrentUserId)) {
             return false;
-        } else if (isKeyguardShowing() && isKeyguardSecure()) {
+        } else if (mStatusBarKeyguardViewManager == null ||
+                (isKeyguardShowing() && isKeyguardSecure())) {
             // Check if the admin has disabled the camera specifically for the keyguard
             return (mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUserId)
                     & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0;
@@ -5590,10 +5429,6 @@
 
     protected int mZenMode;
 
-    // which notification is currently being longpress-examined by the user
-    private NotificationGuts mNotificationGutsExposed;
-    private MenuItem mGutsMenuItem;
-
     protected NotificationShelf mNotificationShelf;
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
@@ -5604,8 +5439,6 @@
 
     protected boolean mVrMode;
 
-    private Set<String> mNonBlockablePkgs;
-
     public boolean isDeviceInteractive() {
         return mDeviceInteractive;
     }
@@ -5877,6 +5710,9 @@
                 userSwitched(mCurrentUserId);
             } else if (Intent.ACTION_USER_ADDED.equals(action)) {
                 updateCurrentProfilesCache();
+            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+                // Start the overview connection to the launcher service
+                Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
             } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                 List<ActivityManager.RecentTaskInfo> recentTask = null;
                 try {
@@ -6134,26 +5970,8 @@
         return mGroupManager;
     }
 
-    public boolean isMediaNotification(NotificationData.Entry entry) {
-        // TODO: confirm that there's a valid media key
-        return entry.getExpandedContentView() != null &&
-               entry.getExpandedContentView()
-                       .findViewById(com.android.internal.R.id.media_actions) != null;
-    }
-
-    // The button in the guts that links to the system notification settings for that app
-    private void startAppNotificationSettingsActivity(String packageName, final int appUid,
-            final NotificationChannel channel) {
-        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
-        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
-        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
-        if (channel != null) {
-            intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
-        }
-        startNotificationGutsIntent(intent, appUid);
-    }
-
-    private void startNotificationGutsIntent(final Intent intent, final int appUid) {
+    @Override
+    public void startNotificationGutsIntent(final Intent intent, final int appUid) {
         dismissKeyguardThenExecute(() -> {
             AsyncTask.execute(() -> {
                 TaskStackBuilder.create(mContext)
@@ -6176,231 +5994,8 @@
         }
     }
 
-    private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
-        row.inflateGuts();
-        row.setGutsView(item);
-        final StatusBarNotification sbn = row.getStatusBarNotification();
-        row.setTag(sbn.getPackageName());
-        final NotificationGuts guts = row.getGuts();
-        guts.setClosedListener((NotificationGuts g) -> {
-            if (!g.willBeRemoved() && !row.isRemoved()) {
-                mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
-            }
-            if (mNotificationGutsExposed == g) {
-                mNotificationGutsExposed = null;
-                mGutsMenuItem = null;
-            }
-            String key = sbn.getKey();
-            if (key.equals(mKeyToRemoveOnGutsClosed)) {
-                mKeyToRemoveOnGutsClosed = null;
-                removeNotification(key, mLatestRankingMap);
-            }
-        });
-
-        View gutsView = item.getGutsView();
-        if (gutsView instanceof NotificationSnooze) {
-            NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
-            snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
-            snoozeGuts.setStatusBarNotification(sbn);
-            snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
-            guts.setHeightChangedListener((NotificationGuts g) -> {
-                mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
-            });
-        }
-
-        if (gutsView instanceof NotificationInfo) {
-            final UserHandle userHandle = sbn.getUser();
-            PackageManager pmUser = getPackageManagerForUser(mContext,
-                    userHandle.getIdentifier());
-            final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
-                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-            final String pkg = sbn.getPackageName();
-            NotificationInfo info = (NotificationInfo) gutsView;
-            // Settings link is only valid for notifications that specify a user, unless this is the
-            // system user.
-            NotificationInfo.OnSettingsClickListener onSettingsClick = null;
-            if (!userHandle.equals(UserHandle.ALL) || mCurrentUserId == UserHandle.USER_SYSTEM) {
-                onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
-                    mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
-                    guts.resetFalsingCheck();
-                    startAppNotificationSettingsActivity(pkg, appUid, channel);
-                };
-            }
-            final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
-                    Intent intent) -> {
-                mMetricsLogger.action(MetricsEvent.ACTION_APP_NOTE_SETTINGS);
-                guts.resetFalsingCheck();
-                startNotificationGutsIntent(intent, sbn.getUid());
-            };
-            final View.OnClickListener onDoneClick = (View v) -> {
-                saveAndCloseNotificationMenu(row, guts, v);
-            };
-            final NotificationInfo.CheckSaveListener checkSaveListener =
-                    (Runnable saveImportance) -> {
-                // If the user has security enabled, show challenge if the setting is changed.
-                if (isLockscreenPublicMode(userHandle.getIdentifier())
-                        && (mState == StatusBarState.KEYGUARD
-                                || mState == StatusBarState.SHADE_LOCKED)) {
-                    onLockedNotificationImportanceChange(() -> {
-                        saveImportance.run();
-                        return true;
-                    });
-                } else {
-                    saveImportance.run();
-                }
-            };
-
-            ArraySet<NotificationChannel> channels = new ArraySet<>();
-            channels.add(row.getEntry().channel);
-            if (row.isSummaryWithChildren()) {
-                // If this is a summary, then add in the children notification channels for the
-                // same user and pkg.
-                final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
-                final int numChildren = childrenRows.size();
-                for (int i = 0; i < numChildren; i++) {
-                    final ExpandableNotificationRow childRow = childrenRows.get(i);
-                    final NotificationChannel childChannel = childRow.getEntry().channel;
-                    final StatusBarNotification childSbn = childRow.getStatusBarNotification();
-                    if (childSbn.getUser().equals(userHandle) &&
-                            childSbn.getPackageName().equals(pkg)) {
-                        channels.add(childChannel);
-                    }
-                }
-            }
-            try {
-                info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
-                        row.getEntry().channel.getImportance(), sbn, onSettingsClick,
-                        onAppSettingsClick, onDoneClick, checkSaveListener,
-                        mNonBlockablePkgs);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        }
-    }
-
-    private void saveAndCloseNotificationMenu(
-            ExpandableNotificationRow row, NotificationGuts guts, View done) {
-        guts.resetFalsingCheck();
-        int[] rowLocation = new int[2];
-        int[] doneLocation = new int[2];
-        row.getLocationOnScreen(rowLocation);
-        done.getLocationOnScreen(doneLocation);
-
-        final int centerX = done.getWidth() / 2;
-        final int centerY = done.getHeight() / 2;
-        final int x = doneLocation[0] - rowLocation[0] + centerX;
-        final int y = doneLocation[1] - rowLocation[1] + centerY;
-        closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
-                true /* removeControls */, x, y, true /* resetMenu */);
-    }
-
     protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
-        return new ExpandableNotificationRow.LongPressListener() {
-            @Override
-            public boolean onLongPress(View v, final int x, final int y,
-                    MenuItem item) {
-                if (!(v instanceof ExpandableNotificationRow)) {
-                    return false;
-                }
-
-                if (v.getWindowToken() == null) {
-                    Log.e(TAG, "Trying to show notification guts, but not attached to window");
-                    return false;
-                }
-
-                final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-                if (row.isDark()) {
-                    return false;
-                }
-                v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-                if (row.areGutsExposed()) {
-                    closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
-                            true /* removeControls */, -1 /* x */, -1 /* y */,
-                            true /* resetMenu */);
-                    return true;
-                }
-                bindGuts(row, item);
-                NotificationGuts guts = row.getGuts();
-
-                // Assume we are a status_bar_notification_row
-                if (guts == null) {
-                    // This view has no guts. Examples are the more card or the dismiss all view
-                    return false;
-                }
-
-                mMetricsLogger.action(MetricsEvent.ACTION_NOTE_CONTROLS);
-
-                // ensure that it's laid but not visible until actually laid out
-                guts.setVisibility(View.INVISIBLE);
-                // Post to ensure the the guts are properly laid out.
-                guts.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (row.getWindowToken() == null) {
-                            Log.e(TAG, "Trying to show notification guts, but not attached to "
-                                    + "window");
-                            return;
-                        }
-                        closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
-                                true /* removeControls */, -1 /* x */, -1 /* y */,
-                                false /* resetMenu */);
-                        guts.setVisibility(View.VISIBLE);
-                        final double horz = Math.max(guts.getWidth() - x, x);
-                        final double vert = Math.max(guts.getHeight() - y, y);
-                        final float r = (float) Math.hypot(horz, vert);
-                        final Animator a
-                                = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
-                        a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                        a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-                        a.addListener(new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationEnd(Animator animation) {
-                                super.onAnimationEnd(animation);
-                                // Move the notification view back over the menu
-                                row.resetTranslation();
-                            }
-                        });
-                        a.start();
-                        final boolean needsFalsingProtection =
-                                (mState == StatusBarState.KEYGUARD &&
-                                !mAccessibilityManager.isTouchExplorationEnabled());
-                        guts.setExposed(true /* exposed */, needsFalsingProtection);
-                        row.closeRemoteInput();
-                        mStackScroller.onHeightChanged(row, true /* needsAnimation */);
-                        mNotificationGutsExposed = guts;
-                        mGutsMenuItem = item;
-                    }
-                });
-                return true;
-            }
-        };
-    }
-
-    /**
-     * Returns the exposed NotificationGuts or null if none are exposed.
-     */
-    public NotificationGuts getExposedGuts() {
-        return mNotificationGutsExposed;
-    }
-
-    /**
-     * Closes guts or notification menus that might be visible and saves any changes.
-     *
-     * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
-     * @param force true if guts should be closed regardless of state (used for snooze only).
-     * @param removeControls true if controls (e.g. info) should be closed.
-     * @param x if closed based on touch location, this is the x touch location.
-     * @param y if closed based on touch location, this is the y touch location.
-     * @param resetMenu if any notification menus that might be revealed should be closed.
-     */
-    public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
-            int x, int y, boolean resetMenu) {
-        if (mNotificationGutsExposed != null) {
-            mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
-        }
-        if (resetMenu) {
-            mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
-        }
+        return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item);
     }
 
     @Override
@@ -6789,7 +6384,7 @@
                 if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
                     // Release the HUN notification to the shade.
 
-                    if (isPanelFullyCollapsed()) {
+                    if (isPresenterFullyCollapsed()) {
                         HeadsUpManager.setIsClickedNotification(row, true);
                     }
                     //
@@ -6921,7 +6516,7 @@
         if (mVisible != visible) {
             mVisible = visible;
             if (!visible) {
-                closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+                mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
                         true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
             }
         }
@@ -7091,7 +6686,9 @@
     }
 
     public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
-        return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
+        return mShowLockscreenNotifications
+                && ((mDisabled2 & DISABLE2_NOTIFICATION_SHADE) == 0)
+                && !mNotificationData.isAmbient(sbn.getKey());
     }
 
     // extended in StatusBar
@@ -7103,7 +6700,7 @@
         mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
     }
 
-    private void updateLockscreenNotificationSetting() {
+    protected void updateLockscreenNotificationSetting() {
         final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
                 1,
@@ -7141,8 +6738,8 @@
         }
         mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
         mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
-        if (key.equals(mKeyToRemoveOnGutsClosed)) {
-            mKeyToRemoveOnGutsClosed = null;
+        if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+            mGutsManager.setKeyToRemoveOnGutsClosed(null);
             Log.w(TAG, "Notification that was kept for guts was updated. " + key);
         }
 
@@ -7241,6 +6838,15 @@
             return false;
         }
 
+        if (mIsOccluded && !isDozing()) {
+            boolean devicePublic = isLockscreenPublicMode(mCurrentUserId);
+            boolean userPublic = devicePublic || isLockscreenPublicMode(sbn.getUserId());
+            boolean needsRedaction = needsRedaction(entry);
+            if (userPublic && needsRedaction) {
+                return false;
+            }
+        }
+
         if (sbn.getNotification().fullScreenIntent != null) {
             if (mAccessibilityManager.isTouchExplorationEnabled()) {
                 if (DEBUG) Log.d(TAG, "No peeking: accessible fullscreen: " + sbn.getKey());
@@ -7340,4 +6946,43 @@
             mNavigationBar.getBarTransitions().setAutoDim(true);
         }
     };
+
+    public NotificationGutsManager getGutsManager() {
+        return mGutsManager;
+    }
+
+    @Override
+    public boolean isPresenterLocked() {
+        return mState == StatusBarState.KEYGUARD;
+    }
+
+    @Override
+    public int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    @Override
+    public NotificationData getNotificationData() {
+        return mNotificationData;
+    }
+
+    @Override
+    public RankingMap getLatestRankingMap() {
+        return mLatestRankingMap;
+    }
+
+    final NotificationInfo.CheckSaveListener mCheckSaveListener =
+            (Runnable saveImportance, StatusBarNotification sbn) -> {
+                // If the user has security enabled, show challenge if the setting is changed.
+                if (isLockscreenPublicMode(sbn.getUser().getIdentifier()) && (
+                        mState == StatusBarState.KEYGUARD
+                                || mState == StatusBarState.SHADE_LOCKED)) {
+                    onLockedNotificationImportanceChange(() -> {
+                        saveImportance.run();
+                        return true;
+                    });
+                } else {
+                    saveImportance.run();
+                }
+            };
 }
diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 1e14626..efe049a 100644
--- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1343,7 +1343,7 @@
         }
 
         // Check if we need to clear any snooze leavebehinds
-        NotificationGuts guts = mStatusBar.getExposedGuts();
+        NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
         if (guts != null && !isTouchInView(ev, guts)
                 && guts.getGutsContent() instanceof NotificationSnooze) {
             NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
@@ -2508,12 +2508,13 @@
         }
         // Check if we need to clear any snooze leavebehinds
         boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
-        NotificationGuts guts = mStatusBar.getExposedGuts();
+        NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
         if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt
                 && !scrollWantsIt) {
             mCheckForLeavebehind = false;
-            mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
-                    false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+            mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                    false /* resetMenu */);
         }
         if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
             mCheckForLeavebehind = true;
@@ -3065,7 +3066,7 @@
             }
             if (!childWasSwipedOut) {
                 Rect clipBounds = child.getClipBounds();
-                childWasSwipedOut = clipBounds.height() == 0;
+                childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
             }
             int animationType = childWasSwipedOut
                     ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
@@ -3355,8 +3356,9 @@
 
     public void checkSnoozeLeavebehind() {
         if (mCheckForLeavebehind) {
-            mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
-                    false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+            mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                    false /* resetMenu */);
             mCheckForLeavebehind = false;
         }
     }
@@ -4438,8 +4440,9 @@
                 // of the panel early.
                 handleChildDismissed(view);
             }
-            mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
-                    false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+            mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+                    false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+                    false /* resetMenu */);
             handleMenuCoveredOrDismissed();
         }
 
@@ -4524,7 +4527,7 @@
         }
 
         public void closeControlsIfOutsideTouch(MotionEvent ev) {
-            NotificationGuts guts = mStatusBar.getExposedGuts();
+            NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
             View view = null;
             if (guts != null && !guts.getGutsContent().isLeavebehind()) {
                 // Only close visible guts if they're not a leavebehind.
@@ -4536,8 +4539,9 @@
             }
             if (view != null && !isTouchInView(ev, view)) {
                 // Touch was outside visible guts / menu notification, close what's visible
-                mStatusBar.closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
-                        true /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+                mStatusBar.getGutsManager().closeAndSaveGuts(false /* removeLeavebehind */,
+                        false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
+                        false /* resetMenu */);
                 resetExposedMenuView(true /* animate */, true /* force */);
             }
         }
diff --git a/com/android/systemui/tuner/PluginFragment.java b/com/android/systemui/tuner/PluginFragment.java
index f91e45d..27bf534 100644
--- a/com/android/systemui/tuner/PluginFragment.java
+++ b/com/android/systemui/tuner/PluginFragment.java
@@ -169,16 +169,23 @@
         protected boolean persistBoolean(boolean value) {
             final int desiredState = value ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                     : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+            boolean shouldSendBroadcast = false;
             for (int i = 0; i < mInfo.services.length; i++) {
                 ComponentName componentName = new ComponentName(mInfo.packageName,
                         mInfo.services[i].name);
-                mPm.setComponentEnabledSetting(componentName, desiredState,
-                        PackageManager.DONT_KILL_APP);
+
+                if (mPm.getComponentEnabledSetting(componentName) != desiredState) {
+                    mPm.setComponentEnabledSetting(componentName, desiredState,
+                            PackageManager.DONT_KILL_APP);
+                    shouldSendBroadcast = true;
+                }
             }
-            final String pkg = mInfo.packageName;
-            final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
-                    pkg != null ? Uri.fromParts("package", pkg, null) : null);
-            getContext().sendBroadcast(intent);
+            if (shouldSendBroadcast) {
+                final String pkg = mInfo.packageName;
+                final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
+                        pkg != null ? Uri.fromParts("package", pkg, null) : null);
+                getContext().sendBroadcast(intent);
+            }
             return true;
         }
 
diff --git a/com/android/systemui/usb/UsbConfirmActivity.java b/com/android/systemui/usb/UsbConfirmActivity.java
index 3eccccd..e117969 100644
--- a/com/android/systemui/usb/UsbConfirmActivity.java
+++ b/com/android/systemui/usb/UsbConfirmActivity.java
@@ -71,10 +71,12 @@
         ap.mIcon = mResolveInfo.loadIcon(packageManager);
         ap.mTitle = appName;
         if (mDevice == null) {
-            ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName);
+            ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName,
+                    mAccessory.getDescription());
             mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
         } else {
-            ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName);
+            ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName,
+                    mDevice.getProductName());
             mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
         }
         ap.mPositiveButtonText = getString(android.R.string.ok);
@@ -88,9 +90,11 @@
         ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
         mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
         if (mDevice == null) {
-            mAlwaysUse.setText(R.string.always_use_accessory);
+            mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
+                    mAccessory.getDescription()));
         } else {
-            mAlwaysUse.setText(R.string.always_use_device);
+            mAlwaysUse.setText(getString(R.string.always_use_device, appName,
+                    mDevice.getProductName()));
         }
         mAlwaysUse.setOnCheckedChangeListener(this);
         mClearDefaultHint = (TextView)ap.mView.findViewById(
diff --git a/com/android/systemui/usb/UsbPermissionActivity.java b/com/android/systemui/usb/UsbPermissionActivity.java
index 1e69fc5..87d11b2 100644
--- a/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/com/android/systemui/usb/UsbPermissionActivity.java
@@ -16,13 +16,17 @@
 
 package com.android.systemui.usb;
 
+import android.annotation.NonNull;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
 import android.hardware.usb.IUsbManager;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
@@ -41,8 +45,13 @@
 
 import com.android.internal.app.AlertActivity;
 import com.android.internal.app.AlertController;
+import com.android.internal.util.XmlUtils;
+import android.hardware.usb.AccessoryFilter;
+import android.hardware.usb.DeviceFilter;
 import com.android.systemui.R;
 
+import org.xmlpull.v1.XmlPullParser;
+
 public class UsbPermissionActivity extends AlertActivity
         implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
 
@@ -84,10 +93,12 @@
         ap.mIcon = aInfo.loadIcon(packageManager);
         ap.mTitle = appName;
         if (mDevice == null) {
-            ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName);
+            ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName,
+                    mAccessory.getDescription());
             mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
         } else {
-            ap.mMessage = getString(R.string.usb_device_permission_prompt, appName);
+            ap.mMessage = getString(R.string.usb_device_permission_prompt, appName,
+                    mDevice.getProductName());
             mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
         }
         ap.mPositiveButtonText = getString(android.R.string.ok);
@@ -95,25 +106,123 @@
         ap.mPositiveButtonListener = this;
         ap.mNegativeButtonListener = this;
 
-        // add "always use" checkbox
-        LayoutInflater inflater = (LayoutInflater)getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-        ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
-        mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
-        if (mDevice == null) {
-            mAlwaysUse.setText(R.string.always_use_accessory);
-        } else {
-            mAlwaysUse.setText(R.string.always_use_device);
+        try {
+            PackageInfo packageInfo = packageManager.getPackageInfo(mPackageName,
+                    PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+            if ((mDevice != null && canBeDefault(mDevice, packageInfo))
+                    || (mAccessory != null && canBeDefault(mAccessory, packageInfo))) {
+                // add "open when" checkbox
+                LayoutInflater inflater = (LayoutInflater) getSystemService(
+                        Context.LAYOUT_INFLATER_SERVICE);
+                ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
+                mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
+                if (mDevice == null) {
+                    mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
+                            mAccessory.getDescription()));
+                } else {
+                    mAlwaysUse.setText(getString(R.string.always_use_device, appName,
+                            mDevice.getProductName()));
+                }
+                mAlwaysUse.setOnCheckedChangeListener(this);
+
+                mClearDefaultHint = (TextView)ap.mView.findViewById(
+                        com.android.internal.R.id.clearDefaultHint);
+                mClearDefaultHint.setVisibility(View.GONE);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // ignore
         }
-        mAlwaysUse.setOnCheckedChangeListener(this);
-        mClearDefaultHint = (TextView)ap.mView.findViewById(
-                                                    com.android.internal.R.id.clearDefaultHint);
-        mClearDefaultHint.setVisibility(View.GONE);
 
         setupAlert();
 
     }
 
+    /**
+     * Can the app be the default for the USB device. I.e. can the app be launched by default if
+     * the device is plugged in.
+     *
+     * @param device The device the app would be default for
+     * @param packageInfo The package info of the app
+     *
+     * @return {@code true} iff the app can be default
+     */
+    private boolean canBeDefault(@NonNull UsbDevice device, @NonNull PackageInfo packageInfo) {
+        ActivityInfo[] activities = packageInfo.activities;
+        if (activities != null) {
+            int numActivities = activities.length;
+            for (int i = 0; i < numActivities; i++) {
+                ActivityInfo activityInfo = activities[i];
+
+                try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
+                        UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+                    if (parser == null) {
+                        continue;
+                    }
+
+                    XmlUtils.nextElement(parser);
+                    while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                        if ("usb-device".equals(parser.getName())) {
+                            DeviceFilter filter = DeviceFilter.read(parser);
+                            if (filter.matches(device)) {
+                                return true;
+                            }
+                        }
+
+                        XmlUtils.nextElement(parser);
+                    }
+                } catch (Exception e) {
+                    Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Can the app be the default for the USB accessory. I.e. can the app be launched by default if
+     * the accessory is plugged in.
+     *
+     * @param accessory The accessory the app would be default for
+     * @param packageInfo The package info of the app
+     *
+     * @return {@code true} iff the app can be default
+     */
+    private boolean canBeDefault(@NonNull UsbAccessory accessory,
+            @NonNull PackageInfo packageInfo) {
+        ActivityInfo[] activities = packageInfo.activities;
+        if (activities != null) {
+            int numActivities = activities.length;
+            for (int i = 0; i < numActivities; i++) {
+                ActivityInfo activityInfo = activities[i];
+
+                try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
+                        UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+                    if (parser == null) {
+                        continue;
+                    }
+
+                    XmlUtils.nextElement(parser);
+                    while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                        if ("usb-accessory".equals(parser.getName())) {
+                            AccessoryFilter filter = AccessoryFilter.read(parser);
+                            if (filter.matches(accessory)) {
+                                return true;
+                            }
+                        }
+
+                        XmlUtils.nextElement(parser);
+                    }
+                } catch (Exception e) {
+                    Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
+                }
+            }
+        }
+
+        return false;
+    }
+
     @Override
     public void onDestroy() {
         IBinder b = ServiceManager.getService(USB_SERVICE);
diff --git a/com/android/systemui/volume/SystemUIInterpolators.java b/com/android/systemui/volume/SystemUIInterpolators.java
new file mode 100644
index 0000000..5ad8840
--- /dev/null
+++ b/com/android/systemui/volume/SystemUIInterpolators.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import android.animation.TimeInterpolator;
+
+public class SystemUIInterpolators {
+    public static final class LogDecelerateInterpolator implements TimeInterpolator {
+        private final float mBase;
+        private final float mDrift;
+        private final float mTimeScale;
+        private final float mOutputScale;
+
+        public LogDecelerateInterpolator() {
+            this(400f, 1.4f, 0);
+        }
+
+        private LogDecelerateInterpolator(float base, float timeScale, float drift) {
+            mBase = base;
+            mDrift = drift;
+            mTimeScale = 1f / timeScale;
+
+            mOutputScale = 1f / computeLog(1f);
+        }
+
+        private float computeLog(float t) {
+            return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
+        }
+
+        @Override
+        public float getInterpolation(float t) {
+            return computeLog(t) * mOutputScale;
+        }
+    }
+
+    public static final class LogAccelerateInterpolator implements TimeInterpolator {
+        private final int mBase;
+        private final int mDrift;
+        private final float mLogScale;
+
+        public LogAccelerateInterpolator() {
+            this(100, 0);
+        }
+
+        private LogAccelerateInterpolator(int base, int drift) {
+            mBase = base;
+            mDrift = drift;
+            mLogScale = 1f / computeLog(1, mBase, mDrift);
+        }
+
+        private static float computeLog(float t, int base, int drift) {
+            return (float) -Math.pow(base, -t) + 1 + (drift * t);
+        }
+
+        @Override
+        public float getInterpolation(float t) {
+            return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
+        }
+    }
+
+    public interface Callback {
+        void onAnimatingChanged(boolean animating);
+    }
+}
diff --git a/com/android/systemui/volume/VolumeDialogComponent.java b/com/android/systemui/volume/VolumeDialogComponent.java
index f6d36e8..4dff9bd 100644
--- a/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/com/android/systemui/volume/VolumeDialogComponent.java
@@ -62,7 +62,6 @@
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
             ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
             | ActivityInfo.CONFIG_ASSETS_PATHS);
-    private final Extension mExtension;
     private VolumeDialog mDialog;
     private VolumePolicy mVolumePolicy = new VolumePolicy(
             DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT,  // volumeDownToEnterSilent
@@ -79,7 +78,7 @@
         // Allow plugins to reference the VolumeDialogController.
         Dependency.get(PluginDependencyProvider.class)
                 .allowPluginDependency(VolumeDialogController.class);
-        mExtension = Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
+        Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
                 .withPlugin(VolumeDialog.class)
                 .withDefault(this::createDefault)
                 .withCallback(dialog -> {
@@ -96,7 +95,6 @@
 
     private VolumeDialog createDefault() {
         VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
-        impl.setStreamImportant(AudioManager.STREAM_ALARM, true);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
         impl.setSilentMode(false);
@@ -152,7 +150,7 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mConfigChanges.applyNewConfig(mContext.getResources())) {
-            mExtension.reload();
+            mController.mCallbacks.onConfigurationChanged();
         }
     }
 
diff --git a/com/android/systemui/volume/VolumeDialogImpl.java b/com/android/systemui/volume/VolumeDialogImpl.java
index 761e979..4b8f581 100644
--- a/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/com/android/systemui/volume/VolumeDialogImpl.java
@@ -19,6 +19,8 @@
 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
 
+import static com.android.systemui.volume.Events.DISMISS_REASON_TOUCH_OUTSIDE;
+
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.animation.ObjectAnimator;
 import android.annotation.NonNull;
@@ -26,16 +28,13 @@
 import android.app.Dialog;
 import android.app.KeyguardManager;
 import android.content.Context;
-import android.content.pm.PackageManager;
+import android.content.DialogInterface;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
-import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.os.Debug;
@@ -44,15 +43,10 @@
 import android.os.Message;
 import android.os.SystemClock;
 import android.provider.Settings.Global;
-import android.transition.AutoTransition;
-import android.transition.Transition;
-import android.transition.TransitionManager;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.view.ContextThemeWrapper;
-import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
@@ -60,7 +54,6 @@
 import android.view.View.OnClickListener;
 import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
@@ -74,16 +67,13 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
 import com.android.systemui.Interpolators;
-import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
 import com.android.systemui.plugins.VolumeDialogController.StreamState;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerZenModePanel;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -96,11 +86,9 @@
  *
  * Methods ending in "H" must be called on the (ui) handler.
  */
-public class VolumeDialogImpl implements VolumeDialog, TunerService.Tunable {
+public class VolumeDialogImpl implements VolumeDialog {
     private static final String TAG = Util.logTag(VolumeDialogImpl.class);
 
-    public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
-
     private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
     private static final int UPDATE_ANIMATION_DURATION = 80;
 
@@ -109,29 +97,22 @@
     private final VolumeDialogController mController;
 
     private Window mWindow;
+    private HardwareUiLayout mHardwareLayout;
     private CustomDialog mDialog;
     private ViewGroup mDialogView;
     private ViewGroup mDialogRowsView;
     private ViewGroup mDialogContentView;
-    private ImageButton mExpandButton;
     private final List<VolumeRow> mRows = new ArrayList<>();
     private ConfigurableTexts mConfigurableTexts;
     private final SparseBooleanArray mDynamic = new SparseBooleanArray();
     private final KeyguardManager mKeyguard;
-    private final AudioManager mAudioManager;
     private final AccessibilityManager mAccessibilityMgr;
-    private int mExpandButtonAnimationDuration;
-    private ZenFooter mZenFooter;
     private final Object mSafetyWarningLock = new Object();
     private final Accessibility mAccessibility = new Accessibility();
     private final ColorStateList mActiveSliderTint;
     private final ColorStateList mInactiveSliderTint;
-    private VolumeDialogMotion mMotion;
-    private int mWindowType;
-    private final ZenModeController mZenModeController;
 
     private boolean mShowing;
-    private boolean mExpanded;
     private boolean mShowA11yStream;
 
     private int mActiveStream;
@@ -139,24 +120,13 @@
     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
     private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
     private State mState;
-    private boolean mExpandButtonAnimationRunning;
     private SafetyWarningDialog mSafetyWarning;
-    private Callback mCallback;
-    private boolean mPendingStateChanged;
-    private boolean mPendingRecheckAll;
-    private long mCollapseTime;
     private boolean mHovering = false;
-    private int mDensity;
-
-    private boolean mShowFullZen;
-    private TunerZenModePanel mZenPanel;
 
     public VolumeDialogImpl(Context context) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
-        mZenModeController = Dependency.get(ZenModeController.class);
         mController = Dependency.get(VolumeDialogController.class);
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mAccessibilityMgr =
                 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
         mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
@@ -164,29 +134,18 @@
     }
 
     public void init(int windowType, Callback callback) {
-        mCallback = callback;
-        mWindowType = windowType;
-
         initDialog();
 
         mAccessibility.init();
 
         mController.addCallback(mControllerCallbackH, mHandler);
         mController.getState();
-        Dependency.get(TunerService.class).addTunable(this, SHOW_FULL_ZEN);
-
-        final Configuration currentConfig = mContext.getResources().getConfiguration();
-        mDensity = currentConfig.densityDpi;
     }
 
     @Override
     public void destroy() {
         mAccessibility.destroy();
         mController.removeCallback(mControllerCallbackH);
-        if (mZenFooter != null) {
-            mZenFooter.cleanup();
-        }
-        Dependency.get(TunerService.class).removeTunable(this);
         mHandler.removeCallbacksAndMessages(null);
     }
 
@@ -199,25 +158,16 @@
         mWindow = mDialog.getWindow();
         mWindow.requestFeature(Window.FEATURE_NO_TITLE);
         mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
-        mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
-        mDialog.setCanceledOnTouchOutside(true);
-        final Resources res = mContext.getResources();
-        final WindowManager.LayoutParams lp = mWindow.getAttributes();
-        lp.type = mWindowType;
-        lp.format = PixelFormat.TRANSLUCENT;
-        lp.setTitle(VolumeDialogImpl.class.getSimpleName());
-        lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
-        lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
-        lp.gravity = Gravity.TOP;
-        lp.windowAnimations = -1;
-        mWindow.setAttributes(lp);
-        mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+        mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+        mWindow.addFlags(
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+        mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+        mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
 
         mDialog.setContentView(R.layout.volume_dialog);
         mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
@@ -231,33 +181,11 @@
                 return true;
             }
         });
+        mHardwareLayout = HardwareUiLayout.get(mDialogView);
+        mHardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE));
 
         mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content);
         mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows);
-        mExpanded = false;
-        mExpandButton = (ImageButton) mDialogView.findViewById(R.id.volume_expand_button);
-        mExpandButton.setOnClickListener(mClickExpand);
-
-        mExpandButton.setVisibility(
-                AudioSystem.isSingleVolume(mContext) ? View.GONE : View.VISIBLE);
-        updateWindowWidthH();
-        updateExpandButtonH();
-
-        mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
-                new VolumeDialogMotion.Callback() {
-                    @Override
-                    public void onAnimatingChanged(boolean animating) {
-                        if (animating) return;
-                        if (mPendingStateChanged) {
-                            mHandler.sendEmptyMessage(H.STATE_CHANGED);
-                            mPendingStateChanged = false;
-                        }
-                        if (mPendingRecheckAll) {
-                            mHandler.sendEmptyMessage(H.RECHECK_ALL);
-                            mPendingRecheckAll = false;
-                        }
-                    }
-                });
 
         if (mRows.isEmpty()) {
             addRow(AudioManager.STREAM_MUSIC,
@@ -279,40 +207,12 @@
         } else {
             addExistingRows();
         }
-        mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
-        mZenFooter = (ZenFooter) mDialog.findViewById(R.id.volume_zen_footer);
-        mZenFooter.init(mZenModeController);
-        mZenPanel = (TunerZenModePanel) mDialog.findViewById(R.id.tuner_zen_mode_panel);
-        mZenPanel.init(mZenModeController);
-        mZenPanel.setCallback(mZenPanelCallback);
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (SHOW_FULL_ZEN.equals(key)) {
-            mShowFullZen = newValue != null && Integer.parseInt(newValue) != 0;
-        }
     }
 
     private ColorStateList loadColorStateList(int colorResId) {
         return ColorStateList.valueOf(mContext.getColor(colorResId));
     }
 
-    private void updateWindowWidthH() {
-        final ViewGroup.MarginLayoutParams lp =
-                (ViewGroup.MarginLayoutParams) mDialogView.getLayoutParams();
-        final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
-        if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
-        int w = dm.widthPixels;
-        final int max = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.volume_dialog_panel_width);
-        if (w > max) {
-            w = max;
-        }
-        lp.width = w - lp.getMarginEnd() - lp.getMarginStart();
-        mDialogView.setLayoutParams(lp);
-    }
-
     public void setStreamImportant(int stream, boolean important) {
         mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
     }
@@ -356,14 +256,10 @@
             final VolumeRow row = mRows.get(i);
             initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important);
             mDialogRowsView.addView(row.view);
+            updateVolumeRowH(row);
         }
     }
 
-
-    private boolean isAttached() {
-        return mDialogContentView != null && mDialogContentView.isAttachedToWindow();
-    }
-
     private VolumeRow getActiveRow() {
         for (VolumeRow row : mRows) {
             if (row.stream == mActiveStream) {
@@ -383,14 +279,10 @@
     public void dump(PrintWriter writer) {
         writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
         writer.print("  mShowing: "); writer.println(mShowing);
-        writer.print("  mExpanded: "); writer.println(mExpanded);
-        writer.print("  mExpandButtonAnimationRunning: ");
-        writer.println(mExpandButtonAnimationRunning);
         writer.print("  mActiveStream: "); writer.println(mActiveStream);
         writer.print("  mDynamic: "); writer.println(mDynamic);
         writer.print("  mAutomute: "); writer.println(mAutomute);
         writer.print("  mSilentMode: "); writer.println(mSilentMode);
-        writer.print("  mCollapseTime: "); writer.println(mCollapseTime);
         writer.print("  mAccessibility.mFeedbackEnabled: ");
         writer.println(mAccessibility.mFeedbackEnabled);
     }
@@ -413,9 +305,9 @@
         row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
         row.view.setId(row.stream);
         row.view.setTag(row);
-        row.header = (TextView) row.view.findViewById(R.id.volume_row_header);
+        row.header = row.view.findViewById(R.id.volume_row_header);
         row.header.setId(20 * row.stream);
-        row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);
+        row.slider =  row.view.findViewById(R.id.volume_row_slider);
         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
         row.anim = null;
 
@@ -496,11 +388,27 @@
         rescheduleTimeoutH();
         if (mShowing) return;
         mShowing = true;
-        mMotion.startShow();
+        mHardwareLayout.setTranslationX(getAnimTranslation());
+        mHardwareLayout.setAlpha(0);
+        mHardwareLayout.animate()
+                .alpha(1)
+                .translationX(0)
+                .setDuration(300)
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .withEndAction(() -> {
+                    mDialog.show();
+                    mWindow.getDecorView().requestAccessibilityFocus();
+                })
+                .start();
         Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
         mController.notifyVisible(true);
     }
 
+    private float getAnimTranslation() {
+        return mContext.getResources().getDimension(
+                R.dimen.volume_dialog_panel_width) / 2;
+    }
+
     protected void rescheduleTimeoutH() {
         mHandler.removeMessages(H.DISMISS);
         final int timeout = computeTimeoutH();
@@ -514,28 +422,24 @@
         if (mAccessibility.mFeedbackEnabled) return 20000;
         if (mHovering) return 16000;
         if (mSafetyWarning != null) return 5000;
-        if (mExpanded || mExpandButtonAnimationRunning) return 5000;
         if (mActiveStream == AudioManager.STREAM_MUSIC) return 1500;
-        if (mZenFooter.shouldShowIntroduction()) {
-            return 6000;
-        }
         return 3000;
     }
 
     protected void dismissH(int reason) {
-        if (mMotion.isAnimating()) {
-            return;
-        }
         mHandler.removeMessages(H.DISMISS);
         mHandler.removeMessages(H.SHOW);
         if (!mShowing) return;
         mShowing = false;
-        mMotion.startDismiss(new Runnable() {
-            @Override
-            public void run() {
-                updateExpandedH(false /* expanding */, true /* dismissing */);
-            }
-        });
+        mHardwareLayout.setTranslationX(0);
+        mHardwareLayout.setAlpha(1);
+        mHardwareLayout.animate()
+                .alpha(0)
+                .translationX(getAnimTranslation())
+                .setDuration(300)
+                .withEndAction(() -> mDialog.dismiss())
+                .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+                .start();
         if (mAccessibilityMgr.isEnabled()) {
             AccessibilityEvent event =
                     AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
@@ -555,83 +459,6 @@
         }
     }
 
-    private void updateDialogBottomMarginH() {
-        final long diff = System.currentTimeMillis() - mCollapseTime;
-        final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration();
-        final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
-        final int bottomMargin = collapsing ? mDialogContentView.getHeight() :
-                mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom);
-        if (bottomMargin != mlp.bottomMargin) {
-            if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin);
-            mlp.bottomMargin = bottomMargin;
-            mDialogView.setLayoutParams(mlp);
-        }
-    }
-
-    private long getConservativeCollapseDuration() {
-        return mExpandButtonAnimationDuration * 3;
-    }
-
-    private void prepareForCollapse() {
-        mHandler.removeMessages(H.UPDATE_BOTTOM_MARGIN);
-        mCollapseTime = System.currentTimeMillis();
-        updateDialogBottomMarginH();
-        mHandler.sendEmptyMessageDelayed(H.UPDATE_BOTTOM_MARGIN, getConservativeCollapseDuration());
-    }
-
-    private void updateExpandedH(final boolean expanded, final boolean dismissing) {
-        if (mExpanded == expanded) return;
-        mExpanded = expanded;
-        mExpandButtonAnimationRunning = isAttached();
-        if (D.BUG) Log.d(TAG, "updateExpandedH " + expanded);
-        updateExpandButtonH();
-        updateFooterH();
-        TransitionManager.endTransitions(mDialogView);
-        final VolumeRow activeRow = getActiveRow();
-        if (!dismissing) {
-            mWindow.setLayout(mWindow.getAttributes().width, ViewGroup.LayoutParams.MATCH_PARENT);
-            TransitionManager.beginDelayedTransition(mDialogView, getTransition());
-        }
-        updateRowsH(activeRow);
-        rescheduleTimeoutH();
-    }
-
-    private void updateExpandButtonH() {
-        if (D.BUG) Log.d(TAG, "updateExpandButtonH");
-        mExpandButton.setClickable(!mExpandButtonAnimationRunning);
-        if (!(mExpandButtonAnimationRunning && isAttached())) {
-            final int res = mExpanded ? R.drawable.ic_volume_collapse_animation
-                    : R.drawable.ic_volume_expand_animation;
-            if (hasTouchFeature()) {
-                mExpandButton.setImageResource(res);
-            } else {
-                // if there is no touch feature, show the volume ringer instead
-                mExpandButton.setImageResource(R.drawable.ic_volume_ringer);
-                mExpandButton.setBackgroundResource(0);  // remove gray background emphasis
-            }
-            mExpandButton.setContentDescription(mContext.getString(mExpanded ?
-                    R.string.accessibility_volume_collapse : R.string.accessibility_volume_expand));
-        }
-        if (mExpandButtonAnimationRunning) {
-            final Drawable d = mExpandButton.getDrawable();
-            if (d instanceof AnimatedVectorDrawable) {
-                // workaround to reset drawable
-                final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) d.getConstantState()
-                        .newDrawable();
-                mExpandButton.setImageDrawable(avd);
-                avd.start();
-                mHandler.postDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        mExpandButtonAnimationRunning = false;
-                        updateExpandButtonH();
-                        rescheduleTimeoutH();
-                    }
-                }, mExpandButtonAnimationDuration);
-            }
-        }
-    }
-
     private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) {
         boolean isActive = row == activeRow;
         if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
@@ -639,15 +466,13 @@
         }
 
         // if the active row is accessibility, then continue to display previous
-        // active row since accessibility is dispalyed under it
+        // active row since accessibility is displayed under it
         if (activeRow.stream == AudioSystem.STREAM_ACCESSIBILITY &&
                 row.stream == mPrevActiveStream) {
             return true;
         }
 
-        return mExpanded && row.view.getVisibility() == View.VISIBLE
-                || (mExpanded && (row.important || isActive))
-                || !mExpanded && isActive;
+        return row.important || isActive;
     }
 
     private void updateRowsH(final VolumeRow activeRow) {
@@ -680,13 +505,7 @@
     }
 
     private void onStateChangedH(State state) {
-        final boolean animating = mMotion.isAnimating();
-        if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating);
         mState = state;
-        if (animating) {
-            mPendingStateChanged = true;
-            return;
-        }
         mDynamic.clear();
         // add any new dynamic rows
         for (int i = 0; i < state.states.size(); i++) {
@@ -709,38 +528,6 @@
         for (VolumeRow row : mRows) {
             updateVolumeRowH(row);
         }
-        updateFooterH();
-    }
-
-    private void updateFooterH() {
-        if (D.BUG) Log.d(TAG, "updateFooterH");
-        final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
-        final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
-                && (mAudioManager.isStreamAffectedByRingerMode(mActiveStream) || mExpanded)
-                && !mZenPanel.isEditing();
-
-        TransitionManager.endTransitions(mDialogView);
-        TransitionManager.beginDelayedTransition(mDialogView, getTransition());
-        if (wasVisible != visible && !visible) {
-            prepareForCollapse();
-        }
-        Util.setVisOrGone(mZenFooter, visible);
-        mZenFooter.update();
-
-        final boolean fullWasVisible = mZenPanel.getVisibility() == View.VISIBLE;
-        final boolean fullVisible = mShowFullZen && !visible;
-        if (fullWasVisible != fullVisible) {
-            Util.setVisOrGone(mZenPanel, fullVisible);
-            if (fullVisible) {
-                mZenPanel.setZenState(mState.zenMode);
-                mZenPanel.setDoneListener(new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        mHandler.sendEmptyMessage(H.UPDATE_FOOTER);
-                    }
-                });
-            }
-        }
     }
 
     private void updateVolumeRowH(VolumeRow row) {
@@ -787,19 +574,13 @@
         row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
         final int iconRes =
                 isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
-                : isRingSilent || zenMuted ? row.cachedIconRes
+                : isRingSilent || zenMuted ? row.iconMuteRes
                 : ss.routedToBluetooth ?
                         (ss.muted ? R.drawable.ic_volume_media_bt_mute
                                 : R.drawable.ic_volume_media_bt)
                 : mAutomute && ss.level == 0 ? row.iconMuteRes
                 : (ss.muted ? row.iconMuteRes : row.iconRes);
-        if (iconRes != row.cachedIconRes) {
-            if (row.cachedIconRes != 0 && isRingVibrate) {
-                mController.vibrate();
-            }
-            row.cachedIconRes = iconRes;
-            row.icon.setImageResource(iconRes);
-        }
+        row.icon.setImageResource(iconRes);
         row.iconState =
                 iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
                 : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
@@ -860,7 +641,7 @@
     }
 
     private void updateVolumeRowSliderTintH(VolumeRow row, boolean isActive) {
-        if (isActive && mExpanded) {
+        if (isActive) {
             row.slider.requestFocus();
         }
         final ColorStateList tint = isActive && row.slider.isEnabled() ? mActiveSliderTint
@@ -980,43 +761,6 @@
         }
     }
 
-    private AutoTransition getTransition() {
-        AutoTransition transition = new AutoTransition();
-        transition.setDuration(mExpandButtonAnimationDuration);
-        transition.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        transition.addListener(new Transition.TransitionListener() {
-            @Override
-            public void onTransitionStart(Transition transition) {
-            }
-
-            @Override
-            public void onTransitionEnd(Transition transition) {
-                mWindow.setLayout(
-                        mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
-            }
-
-            @Override
-            public void onTransitionCancel(Transition transition) {
-            }
-
-            @Override
-            public void onTransitionPause(Transition transition) {
-                mWindow.setLayout(
-                        mWindow.getAttributes().width, ViewGroup.LayoutParams.WRAP_CONTENT);
-            }
-
-            @Override
-            public void onTransitionResume(Transition transition) {
-            }
-        });
-        return transition;
-    }
-
-    private boolean hasTouchFeature() {
-        final PackageManager pm = mContext.getPackageManager();
-        return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
-    }
-
     private final VolumeDialogController.Callbacks mControllerCallbackH
             = new VolumeDialogController.Callbacks() {
         @Override
@@ -1046,17 +790,9 @@
 
         @Override
         public void onConfigurationChanged() {
-            Configuration newConfig = mContext.getResources().getConfiguration();
-            final int density = newConfig.densityDpi;
-            if (density != mDensity) {
-                mDialog.dismiss();
-                mZenFooter.cleanup();
-                initDialog();
-                mDensity = density;
-            }
-            updateWindowWidthH();
+            mDialog.dismiss();
+            initDialog();
             mConfigurableTexts.update();
-            mZenFooter.onConfigurationChanged();
         }
 
         @Override
@@ -1092,33 +828,6 @@
         }
     };
 
-    private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() {
-        @Override
-        public void onPrioritySettings() {
-            mCallback.onZenPrioritySettingsClicked();
-        }
-
-        @Override
-        public void onInteraction() {
-            mHandler.sendEmptyMessage(H.RESCHEDULE_TIMEOUT);
-        }
-
-        @Override
-        public void onExpanded(boolean expanded) {
-            // noop.
-        }
-    };
-
-    private final OnClickListener mClickExpand = new OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mExpandButtonAnimationRunning) return;
-            final boolean newExpand = !mExpanded;
-            Events.writeEvent(mContext, Events.EVENT_EXPAND, newExpand);
-            updateExpandedH(newExpand, false /* dismissing */);
-        }
-    };
-
     private final class H extends Handler {
         private static final int SHOW = 1;
         private static final int DISMISS = 2;
@@ -1127,8 +836,6 @@
         private static final int SET_STREAM_IMPORTANT = 5;
         private static final int RESCHEDULE_TIMEOUT = 6;
         private static final int STATE_CHANGED = 7;
-        private static final int UPDATE_BOTTOM_MARGIN = 8;
-        private static final int UPDATE_FOOTER = 9;
 
         public H() {
             super(Looper.getMainLooper());
@@ -1144,15 +851,13 @@
                 case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
                 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
                 case STATE_CHANGED: onStateChangedH(mState); break;
-                case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break;
-                case UPDATE_FOOTER: updateFooterH(); break;
             }
         }
     }
 
-    private final class CustomDialog extends Dialog {
+    private final class CustomDialog extends Dialog implements DialogInterface {
         public CustomDialog(Context context) {
-            super(context);
+            super(context, com.android.systemui.R.style.qs_theme);
         }
 
         @Override
@@ -1162,26 +867,15 @@
         }
 
         @Override
-        protected void onStop() {
-            super.onStop();
-            final boolean animating = mMotion.isAnimating();
-            if (D.BUG) Log.d(TAG, "onStop animating=" + animating);
-            if (animating) {
-                mPendingRecheckAll = true;
-                return;
-            }
-            mHandler.sendEmptyMessage(H.RECHECK_ALL);
+        protected void onStart() {
+            super.setCanceledOnTouchOutside(true);
+            super.onStart();
         }
 
         @Override
-        public boolean onTouchEvent(MotionEvent event) {
-            if (isShowing()) {
-                if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
-                    dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
-                    return true;
-                }
-            }
-            return false;
+        protected void onStop() {
+            super.onStop();
+            mHandler.sendEmptyMessage(H.RECHECK_ALL);
         }
 
         @Override
@@ -1324,7 +1018,6 @@
         private int iconRes;
         private int iconMuteRes;
         private boolean important;
-        private int cachedIconRes;
         private ColorStateList cachedSliderTint;
         private int iconState;  // from Events
         private ObjectAnimator anim;  // slider progress animation for non-touch-related updates
diff --git a/com/android/systemui/volume/VolumeDialogMotion.java b/com/android/systemui/volume/VolumeDialogMotion.java
deleted file mode 100644
index 01d31e2..0000000
--- a/com/android/systemui/volume/VolumeDialogMotion.java
+++ /dev/null
@@ -1,317 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnDismissListener;
-import android.content.DialogInterface.OnShowListener;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.PathInterpolator;
-
-public class VolumeDialogMotion {
-    private static final String TAG = Util.logTag(VolumeDialogMotion.class);
-
-    private static final float ANIMATION_SCALE = 1.0f;
-    private static final int PRE_DISMISS_DELAY = 50;
-
-    private final Dialog mDialog;
-    private final View mDialogView;
-    private final ViewGroup mContents;  // volume rows + zen footer
-    private final View mChevron;
-    private final Handler mHandler = new Handler();
-    private final Callback mCallback;
-
-    private boolean mAnimating;  // show or dismiss animation is running
-    private boolean mShowing;  // show animation is running
-    private boolean mDismissing;  // dismiss animation is running
-    private ValueAnimator mChevronPositionAnimator;
-    private ValueAnimator mContentsPositionAnimator;
-
-    public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents, View chevron,
-            Callback callback) {
-        mDialog = dialog;
-        mDialogView = dialogView;
-        mContents = contents;
-        mChevron = chevron;
-        mCallback = callback;
-        mDialog.setOnDismissListener(new OnDismissListener() {
-            @Override
-            public void onDismiss(DialogInterface dialog) {
-                if (D.BUG) Log.d(TAG, "mDialog.onDismiss");
-            }
-        });
-        mDialog.setOnShowListener(new OnShowListener() {
-            @Override
-            public void onShow(DialogInterface dialog) {
-                if (D.BUG) Log.d(TAG, "mDialog.onShow");
-                final int h = mDialogView.getHeight();
-                mDialogView.setTranslationY(-h);
-                startShowAnimation();
-            }
-        });
-    }
-
-    public boolean isAnimating() {
-        return mAnimating;
-    }
-
-    private void setShowing(boolean showing) {
-        if (showing == mShowing) return;
-        mShowing = showing;
-        if (D.BUG) Log.d(TAG, "mShowing = " + mShowing);
-        updateAnimating();
-    }
-
-    private void setDismissing(boolean dismissing) {
-        if (dismissing == mDismissing) return;
-        mDismissing = dismissing;
-        if (D.BUG) Log.d(TAG, "mDismissing = " + mDismissing);
-        updateAnimating();
-    }
-
-    private void updateAnimating() {
-        final boolean animating = mShowing || mDismissing;
-        if (animating == mAnimating) return;
-        mAnimating = animating;
-        if (D.BUG) Log.d(TAG, "mAnimating = " + mAnimating);
-        if (mCallback != null) {
-            mCallback.onAnimatingChanged(mAnimating);
-        }
-    }
-
-    public void startShow() {
-        if (D.BUG) Log.d(TAG, "startShow");
-        if (mShowing) return;
-        setShowing(true);
-        if (mDismissing) {
-            mDialogView.animate().cancel();
-            setDismissing(false);
-            startShowAnimation();
-            return;
-        }
-        if (D.BUG) Log.d(TAG, "mDialog.show()");
-        mDialog.show();
-    }
-
-    private int chevronDistance() {
-        return mChevron.getHeight() / 6;
-    }
-
-    private int chevronPosY() {
-        final Object tag = mChevron == null ? null : mChevron.getTag();
-        return tag == null ? 0 : (Integer) tag;
-    }
-
-    private void startShowAnimation() {
-        if (D.BUG) Log.d(TAG, "startShowAnimation");
-        mDialogView.animate()
-                .translationY(0)
-                .setDuration(scaledDuration(300))
-                .setInterpolator(new LogDecelerateInterpolator())
-                .setListener(null)
-                .setUpdateListener(animation -> {
-                    if (mChevronPositionAnimator != null) {
-                        final float v = (Float) mChevronPositionAnimator.getAnimatedValue();
-                        if (mChevronPositionAnimator == null) return;
-                        // reposition chevron
-                        final int posY = chevronPosY();
-                        mChevron.setTranslationY(posY + v + -mDialogView.getTranslationY());
-                    }
-                })
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mChevronPositionAnimator == null) return;
-                        // reposition chevron
-                        final int posY = chevronPosY();
-                        mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
-                    }
-                })
-                .start();
-
-        mContentsPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
-                .setDuration(scaledDuration(400));
-        mContentsPositionAnimator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mCancelled) return;
-                if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
-                setShowing(false);
-            }
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
-                mCancelled = true;
-            }
-        });
-        mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float v = (Float) animation.getAnimatedValue();
-                mContents.setTranslationY(v + -mDialogView.getTranslationY());
-            }
-        });
-        mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
-        mContentsPositionAnimator.start();
-
-        mContents.setAlpha(0);
-        mContents.animate()
-                .alpha(1)
-                .setDuration(scaledDuration(150))
-                .setInterpolator(new PathInterpolator(0f, 0f, .2f, 1f))
-                .start();
-
-        mChevronPositionAnimator = ValueAnimator.ofFloat(-chevronDistance(), 0)
-                .setDuration(scaledDuration(250));
-        mChevronPositionAnimator.setInterpolator(new PathInterpolator(.4f, 0f, .2f, 1f));
-        mChevronPositionAnimator.start();
-
-        mChevron.setAlpha(0);
-        mChevron.animate()
-                .alpha(1)
-                .setStartDelay(scaledDuration(50))
-                .setDuration(scaledDuration(150))
-                .setInterpolator(new PathInterpolator(.4f, 0f, 1f, 1f))
-                .start();
-    }
-
-    public void startDismiss(final Runnable onComplete) {
-        if (D.BUG) Log.d(TAG, "startDismiss");
-        if (mDismissing) return;
-        setDismissing(true);
-        if (mShowing) {
-            mDialogView.animate().cancel();
-            if (mContentsPositionAnimator != null) {
-                mContentsPositionAnimator.cancel();
-            }
-            mContents.animate().cancel();
-            if (mChevronPositionAnimator != null) {
-                mChevronPositionAnimator.cancel();
-            }
-            mChevron.animate().cancel();
-            setShowing(false);
-        }
-        mDialogView.animate()
-                .translationY(-mDialogView.getHeight())
-                .setDuration(scaledDuration(250))
-                .setInterpolator(new LogAccelerateInterpolator())
-                .setUpdateListener(new AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        mContents.setTranslationY(-mDialogView.getTranslationY());
-                        final int posY = chevronPosY();
-                        mChevron.setTranslationY(posY + -mDialogView.getTranslationY());
-                    }
-                })
-                .setListener(new AnimatorListenerAdapter() {
-                    private boolean mCancelled;
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        if (mCancelled) return;
-                        if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
-                        mHandler.postDelayed(new Runnable() {
-                            @Override
-                            public void run() {
-                                if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
-                                mDialog.dismiss();
-                                onComplete.run();
-                                setDismissing(false);
-                            }
-                        }, PRE_DISMISS_DELAY);
-
-                    }
-                    @Override
-                    public void onAnimationCancel(Animator animation) {
-                        if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
-                        mCancelled = true;
-                    }
-                }).start();
-    }
-
-    private static int scaledDuration(int base) {
-        return (int) (base * ANIMATION_SCALE);
-    }
-
-    public static final class LogDecelerateInterpolator implements TimeInterpolator {
-        private final float mBase;
-        private final float mDrift;
-        private final float mTimeScale;
-        private final float mOutputScale;
-
-        public LogDecelerateInterpolator() {
-            this(400f, 1.4f, 0);
-        }
-
-        private LogDecelerateInterpolator(float base, float timeScale, float drift) {
-            mBase = base;
-            mDrift = drift;
-            mTimeScale = 1f / timeScale;
-
-            mOutputScale = 1f / computeLog(1f);
-        }
-
-        private float computeLog(float t) {
-            return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
-        }
-
-        @Override
-        public float getInterpolation(float t) {
-            return computeLog(t) * mOutputScale;
-        }
-    }
-
-    public static final class LogAccelerateInterpolator implements TimeInterpolator {
-        private final int mBase;
-        private final int mDrift;
-        private final float mLogScale;
-
-        public LogAccelerateInterpolator() {
-            this(100, 0);
-        }
-
-        private LogAccelerateInterpolator(int base, int drift) {
-            mBase = base;
-            mDrift = drift;
-            mLogScale = 1f / computeLog(1, mBase, mDrift);
-        }
-
-        private static float computeLog(float t, int base, int drift) {
-            return (float) -Math.pow(base, -t) + 1 + (drift * t);
-        }
-
-        @Override
-        public float getInterpolation(float t) {
-            return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
-        }
-    }
-
-    public interface Callback {
-        void onAnimatingChanged(boolean animating);
-    }
-}
diff --git a/com/android/systemui/volume/ZenFooter.java b/com/android/systemui/volume/ZenFooter.java
deleted file mode 100644
index 80e1629..0000000
--- a/com/android/systemui/volume/ZenFooter.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.volume;
-
-import android.animation.LayoutTransition;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.provider.Settings.Global;
-import android.service.notification.ZenModeConfig;
-import android.transition.AutoTransition;
-import android.transition.TransitionManager;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import java.util.Objects;
-
-/**
- * Zen mode information (and end button) attached to the bottom of the volume dialog.
- */
-public class ZenFooter extends LinearLayout {
-    private static final String TAG = Util.logTag(ZenFooter.class);
-
-    private final Context mContext;
-    private final ConfigurableTexts mConfigurableTexts;
-
-    private ImageView mIcon;
-    private TextView mSummaryLine1;
-    private TextView mSummaryLine2;
-    private TextView mEndNowButton;
-    private View mZenIntroduction;
-    private View mZenIntroductionConfirm;
-    private TextView mZenIntroductionMessage;
-    private int mZen = -1;
-    private ZenModeConfig mConfig;
-    private ZenModeController mController;
-
-    public ZenFooter(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mContext = context;
-        mConfigurableTexts = new ConfigurableTexts(mContext);
-        final LayoutTransition layoutTransition = new LayoutTransition();
-        layoutTransition.setDuration(new ValueAnimator().getDuration() / 2);
-        setLayoutTransition(layoutTransition);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mIcon = findViewById(R.id.volume_zen_icon);
-        mSummaryLine1 = findViewById(R.id.volume_zen_summary_line_1);
-        mSummaryLine2 = findViewById(R.id.volume_zen_summary_line_2);
-        mEndNowButton = findViewById(R.id.volume_zen_end_now);
-        mZenIntroduction = findViewById(R.id.zen_introduction);
-        mZenIntroductionMessage = findViewById(R.id.zen_introduction_message);
-        mConfigurableTexts.add(mZenIntroductionMessage, R.string.zen_alarms_introduction);
-        mZenIntroductionConfirm = findViewById(R.id.zen_introduction_confirm);
-        mZenIntroductionConfirm.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                confirmZenIntroduction();
-            }
-        });
-        Util.setVisOrGone(mZenIntroduction, shouldShowIntroduction());
-        mConfigurableTexts.add(mSummaryLine1);
-        mConfigurableTexts.add(mSummaryLine2);
-        mConfigurableTexts.add(mEndNowButton, R.string.volume_zen_end_now);
-    }
-
-    public void init(final ZenModeController controller) {
-        mEndNowButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                setZen(Global.ZEN_MODE_OFF);
-                controller.setZen(Global.ZEN_MODE_OFF, null, TAG);
-            }
-        });
-        mZen = controller.getZen();
-        mConfig = controller.getConfig();
-        mController = controller;
-        mController.addCallback(mZenCallback);
-        update();
-        updateIntroduction();
-    }
-
-    public void cleanup() {
-        mController.removeCallback(mZenCallback);
-    }
-
-    private void setZen(int zen) {
-        if (mZen == zen) return;
-        mZen = zen;
-        update();
-        post(() -> {
-            updateIntroduction();
-        });
-    }
-
-    private void setConfig(ZenModeConfig config) {
-        if (Objects.equals(mConfig, config)) return;
-        mConfig = config;
-        update();
-    }
-
-    private void confirmZenIntroduction() {
-        Prefs.putBoolean(mContext, Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, true);
-        updateIntroduction();
-    }
-
-    private boolean isZenPriority() {
-        return mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-    }
-
-    private boolean isZenAlarms() {
-        return mZen == Global.ZEN_MODE_ALARMS;
-    }
-
-    private boolean isZenNone() {
-        return mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
-    }
-
-    public void update() {
-        mIcon.setImageResource(isZenNone() ? R.drawable.ic_dnd_total_silence : R.drawable.ic_dnd);
-        final String line1 =
-                isZenPriority() ? mContext.getString(R.string.interruption_level_priority)
-                : isZenAlarms() ? mContext.getString(R.string.interruption_level_alarms)
-                : isZenNone() ? mContext.getString(R.string.interruption_level_none)
-                : null;
-        Util.setText(mSummaryLine1, line1);
-
-        final CharSequence line2 = ZenModeConfig.getConditionSummary(mContext, mConfig,
-                                mController.getCurrentUser(), true /*shortVersion*/);
-        Util.setText(mSummaryLine2, line2);
-    }
-
-    public boolean shouldShowIntroduction() {
-        final boolean confirmed =  Prefs.getBoolean(mContext,
-                Prefs.Key.DND_CONFIRMED_ALARM_INTRODUCTION, false);
-        return !confirmed && isZenAlarms();
-    }
-
-    public void updateIntroduction() {
-        Util.setVisOrGone(mZenIntroduction, shouldShowIntroduction());
-    }
-
-    public void onConfigurationChanged() {
-        mConfigurableTexts.update();
-    }
-
-    private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
-        @Override
-        public void onZenChanged(int zen) {
-            setZen(zen);
-        }
-        @Override
-        public void onConfigChanged(ZenModeConfig config) {
-            setConfig(config);
-        }
-    };
-}
diff --git a/java/lang/invoke/ByteArrayVarHandle.java b/java/lang/invoke/ByteArrayViewVarHandle.java
similarity index 69%
rename from java/lang/invoke/ByteArrayVarHandle.java
rename to java/lang/invoke/ByteArrayViewVarHandle.java
index 4237058..abdb1cb 100644
--- a/java/lang/invoke/ByteArrayVarHandle.java
+++ b/java/lang/invoke/ByteArrayViewVarHandle.java
@@ -22,16 +22,16 @@
  * A VarHandle to access byte array elements as an array of primitive types.
  * @hide
  */
-final class ByteArrayVarHandle extends VarHandle {
-    private ByteOrder byteOrder;
+final class ByteArrayViewVarHandle extends VarHandle {
+    private boolean nativeByteOrder;
 
-    private ByteArrayVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
+    private ByteArrayViewVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
         super(arrayClass.getComponentType(), byte[].class, false /* isFinal */,
               byte[].class, int.class);
-        this.byteOrder = byteOrder;
+        this.nativeByteOrder = byteOrder.equals(ByteOrder.nativeOrder());
     }
 
-    static ByteArrayVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
-        return new ByteArrayVarHandle(arrayClass, byteOrder);
+    static ByteArrayViewVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
+        return new ByteArrayViewVarHandle(arrayClass, byteOrder);
     }
 }
diff --git a/java/lang/invoke/ByteBufferViewVarHandle.java b/java/lang/invoke/ByteBufferViewVarHandle.java
index 74f3b0c..75fcbab 100644
--- a/java/lang/invoke/ByteBufferViewVarHandle.java
+++ b/java/lang/invoke/ByteBufferViewVarHandle.java
@@ -24,12 +24,12 @@
  * @hide
  */
 final class ByteBufferViewVarHandle extends VarHandle {
-    private ByteOrder byteOrder;
+    private boolean nativeByteOrder;
 
     private ByteBufferViewVarHandle(Class<?> arrayClass, ByteOrder byteOrder) {
         super(arrayClass.getComponentType(), byte[].class, false /* isFinal */,
               ByteBuffer.class, int.class);
-        this.byteOrder = byteOrder;
+        this.nativeByteOrder = byteOrder.equals(ByteOrder.nativeOrder());
     }
 
     static ByteBufferViewVarHandle create(Class<?> arrayClass, ByteOrder byteOrder) {
diff --git a/java/lang/invoke/CallSite.java b/java/lang/invoke/CallSite.java
index 1ff1eb8..85b4bb9 100644
--- a/java/lang/invoke/CallSite.java
+++ b/java/lang/invoke/CallSite.java
@@ -25,15 +25,363 @@
 
 package java.lang.invoke;
 
+// Android-changed: Not using Empty
+//import sun.invoke.empty.Empty;
+import static java.lang.invoke.MethodHandleStatics.*;
+import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
+
+/**
+ * A {@code CallSite} is a holder for a variable {@link MethodHandle},
+ * which is called its {@code target}.
+ * An {@code invokedynamic} instruction linked to a {@code CallSite} delegates
+ * all calls to the site's current target.
+ * A {@code CallSite} may be associated with several {@code invokedynamic}
+ * instructions, or it may be "free floating", associated with none.
+ * In any case, it may be invoked through an associated method handle
+ * called its {@linkplain #dynamicInvoker dynamic invoker}.
+ * <p>
+ * {@code CallSite} is an abstract class which does not allow
+ * direct subclassing by users.  It has three immediate,
+ * concrete subclasses that may be either instantiated or subclassed.
+ * <ul>
+ * <li>If a mutable target is not required, an {@code invokedynamic} instruction
+ * may be permanently bound by means of a {@linkplain ConstantCallSite constant call site}.
+ * <li>If a mutable target is required which has volatile variable semantics,
+ * because updates to the target must be immediately and reliably witnessed by other threads,
+ * a {@linkplain VolatileCallSite volatile call site} may be used.
+ * <li>Otherwise, if a mutable target is required,
+ * a {@linkplain MutableCallSite mutable call site} may be used.
+ * </ul>
+ * <p>
+ * A non-constant call site may be <em>relinked</em> by changing its target.
+ * The new target must have the same {@linkplain MethodHandle#type() type}
+ * as the previous target.
+ * Thus, though a call site can be relinked to a series of
+ * successive targets, it cannot change its type.
+ * <p>
+ * Here is a sample use of call sites and bootstrap methods which links every
+ * dynamic call site to print its arguments:
+<blockquote><pre>{@code
+static void test() throws Throwable {
+    // THE FOLLOWING LINE IS PSEUDOCODE FOR A JVM INSTRUCTION
+    InvokeDynamic[#bootstrapDynamic].baz("baz arg", 2, 3.14);
+}
+private static void printArgs(Object... args) {
+  System.out.println(java.util.Arrays.deepToString(args));
+}
+private static final MethodHandle printArgs;
+static {
+  MethodHandles.Lookup lookup = MethodHandles.lookup();
+  Class thisClass = lookup.lookupClass();  // (who am I?)
+  printArgs = lookup.findStatic(thisClass,
+      "printArgs", MethodType.methodType(void.class, Object[].class));
+}
+private static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) {
+  // ignore caller and name, but match the type:
+  return new ConstantCallSite(printArgs.asType(type));
+}
+}</pre></blockquote>
+ * @author John Rose, JSR 292 EG
+ */
 abstract
 public class CallSite {
+    // Android-changed: not used.
+    // static { MethodHandleImpl.initStatics(); }
 
-    public MethodType type() { return null; }
+    // The actual payload of this call site:
+    /*package-private*/
+    MethodHandle target;    // Note: This field is known to the JVM.  Do not change.
 
+    /**
+     * Make a blank call site object with the given method type.
+     * An initial target method is supplied which will throw
+     * an {@link IllegalStateException} if called.
+     * <p>
+     * Before this {@code CallSite} object is returned from a bootstrap method,
+     * it is usually provided with a more useful target method,
+     * via a call to {@link CallSite#setTarget(MethodHandle) setTarget}.
+     * @throws NullPointerException if the proposed type is null
+     */
+    /*package-private*/
+    CallSite(MethodType type) {
+        // Android-changed: No cache for these so create uninitializedCallSite target here using
+        // method handle transformations to create a method handle that has the expected method
+        // type but throws an IllegalStateException.
+        // target = makeUninitializedCallSite(type);
+        this.target = MethodHandles.throwException(type.returnType(), IllegalStateException.class);
+        this.target = MethodHandles.insertArguments(
+            this.target, 0, new IllegalStateException("uninitialized call site"));
+        if (type.parameterCount() > 0) {
+            this.target = MethodHandles.dropArguments(this.target, 0, type.ptypes());
+        }
+
+        // Android-changed: Using initializer method for GET_TARGET
+        // rather than complex static initializer.
+        initializeGetTarget();
+    }
+
+    /**
+     * Make a call site object equipped with an initial target method handle.
+     * @param target the method handle which will be the initial target of the call site
+     * @throws NullPointerException if the proposed target is null
+     */
+    /*package-private*/
+    CallSite(MethodHandle target) {
+        target.type();  // null check
+        this.target = target;
+
+        // Android-changed: Using initializer method for GET_TARGET
+        // rather than complex static initializer.
+        initializeGetTarget();
+    }
+
+    /**
+     * Make a call site object equipped with an initial target method handle.
+     * @param targetType the desired type of the call site
+     * @param createTargetHook a hook which will bind the call site to the target method handle
+     * @throws WrongMethodTypeException if the hook cannot be invoked on the required arguments,
+     *         or if the target returned by the hook is not of the given {@code targetType}
+     * @throws NullPointerException if the hook returns a null value
+     * @throws ClassCastException if the hook returns something other than a {@code MethodHandle}
+     * @throws Throwable anything else thrown by the hook function
+     */
+    /*package-private*/
+    CallSite(MethodType targetType, MethodHandle createTargetHook) throws Throwable {
+        this(targetType);
+        ConstantCallSite selfCCS = (ConstantCallSite) this;
+        MethodHandle boundTarget = (MethodHandle) createTargetHook.invokeWithArguments(selfCCS);
+        checkTargetChange(this.target, boundTarget);
+        this.target = boundTarget;
+
+        // Android-changed: Using initializer method for GET_TARGET
+        // rather than complex static initializer.
+        initializeGetTarget();
+    }
+
+    /**
+     * Returns the type of this call site's target.
+     * Although targets may change, any call site's type is permanent, and can never change to an unequal type.
+     * The {@code setTarget} method enforces this invariant by refusing any new target that does
+     * not have the previous target's type.
+     * @return the type of the current target, which is also the type of any future target
+     */
+    public MethodType type() {
+        // warning:  do not call getTarget here, because CCS.getTarget can throw IllegalStateException
+        return target.type();
+    }
+
+    /**
+     * Returns the target method of the call site, according to the
+     * behavior defined by this call site's specific class.
+     * The immediate subclasses of {@code CallSite} document the
+     * class-specific behaviors of this method.
+     *
+     * @return the current linkage state of the call site, its target method handle
+     * @see ConstantCallSite
+     * @see VolatileCallSite
+     * @see #setTarget
+     * @see ConstantCallSite#getTarget
+     * @see MutableCallSite#getTarget
+     * @see VolatileCallSite#getTarget
+     */
     public abstract MethodHandle getTarget();
 
+    /**
+     * Updates the target method of this call site, according to the
+     * behavior defined by this call site's specific class.
+     * The immediate subclasses of {@code CallSite} document the
+     * class-specific behaviors of this method.
+     * <p>
+     * The type of the new target must be {@linkplain MethodType#equals equal to}
+     * the type of the old target.
+     *
+     * @param newTarget the new target
+     * @throws NullPointerException if the proposed new target is null
+     * @throws WrongMethodTypeException if the proposed new target
+     *         has a method type that differs from the previous target
+     * @see CallSite#getTarget
+     * @see ConstantCallSite#setTarget
+     * @see MutableCallSite#setTarget
+     * @see VolatileCallSite#setTarget
+     */
     public abstract void setTarget(MethodHandle newTarget);
 
+    void checkTargetChange(MethodHandle oldTarget, MethodHandle newTarget) {
+        MethodType oldType = oldTarget.type();
+        MethodType newType = newTarget.type();  // null check!
+        if (!newType.equals(oldType))
+            throw wrongTargetType(newTarget, oldType);
+    }
+
+    private static WrongMethodTypeException wrongTargetType(MethodHandle target, MethodType type) {
+        return new WrongMethodTypeException(String.valueOf(target)+" should be of type "+type);
+    }
+
+    /**
+     * Produces a method handle equivalent to an invokedynamic instruction
+     * which has been linked to this call site.
+     * <p>
+     * This method is equivalent to the following code:
+     * <blockquote><pre>{@code
+     * MethodHandle getTarget, invoker, result;
+     * getTarget = MethodHandles.publicLookup().bind(this, "getTarget", MethodType.methodType(MethodHandle.class));
+     * invoker = MethodHandles.exactInvoker(this.type());
+     * result = MethodHandles.foldArguments(invoker, getTarget)
+     * }</pre></blockquote>
+     *
+     * @return a method handle which always invokes this call site's current target
+     */
     public abstract MethodHandle dynamicInvoker();
 
+    /*non-public*/ MethodHandle makeDynamicInvoker() {
+        // Android-changed: Use bindTo() rather than bindArgumentL() (not implemented).
+        MethodHandle getTarget = GET_TARGET.bindTo(this);
+        MethodHandle invoker = MethodHandles.exactInvoker(this.type());
+        return MethodHandles.foldArguments(invoker, getTarget);
+    }
+
+    // Android-changed: no longer final. GET_TARGET assigned in initializeGetTarget().
+    private static MethodHandle GET_TARGET = null;
+
+    private void initializeGetTarget() {
+        // Android-changed: moved from static initializer for
+        // GET_TARGET to avoid issues with running early. Called from
+        // constructors. CallSite creation is not performance critical.
+        synchronized (CallSite.class) {
+            if (GET_TARGET == null) {
+                try {
+                    GET_TARGET = IMPL_LOOKUP.
+                            findVirtual(CallSite.class, "getTarget",
+                                        MethodType.methodType(MethodHandle.class));
+                } catch (ReflectiveOperationException e) {
+                    throw new InternalError(e);
+                }
+            }
+        }
+    }
+
+    // Android-changed: not used.
+    // /** This guy is rolled into the default target if a MethodType is supplied to the constructor. */
+    // /*package-private*/
+    // static Empty uninitializedCallSite() {
+    //     throw new IllegalStateException("uninitialized call site");
+    // }
+
+    // unsafe stuff:
+    private static final long TARGET_OFFSET;
+    static {
+        try {
+            TARGET_OFFSET = UNSAFE.objectFieldOffset(CallSite.class.getDeclaredField("target"));
+        } catch (Exception ex) { throw new Error(ex); }
+    }
+
+    /*package-private*/
+    void setTargetNormal(MethodHandle newTarget) {
+        // Android-changed: Set value directly.
+        // MethodHandleNatives.setCallSiteTargetNormal(this, newTarget);
+        target = newTarget;
+    }
+    /*package-private*/
+    MethodHandle getTargetVolatile() {
+        return (MethodHandle) UNSAFE.getObjectVolatile(this, TARGET_OFFSET);
+    }
+    /*package-private*/
+    void setTargetVolatile(MethodHandle newTarget) {
+        // Android-changed: Set value directly.
+        // MethodHandleNatives.setCallSiteTargetVolatile(this, newTarget);
+        UNSAFE.putObjectVolatile(this, TARGET_OFFSET, newTarget);
+    }
+
+    // Android-changed: not used.
+    // this implements the upcall from the JVM, MethodHandleNatives.makeDynamicCallSite:
+    // static CallSite makeSite(MethodHandle bootstrapMethod,
+    //                          // Callee information:
+    //                          String name, MethodType type,
+    //                          // Extra arguments for BSM, if any:
+    //                          Object info,
+    //                          // Caller information:
+    //                          Class<?> callerClass) {
+    //     MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass);
+    //     CallSite site;
+    //     try {
+    //         Object binding;
+    //         info = maybeReBox(info);
+    //         if (info == null) {
+    //             binding = bootstrapMethod.invoke(caller, name, type);
+    //         } else if (!info.getClass().isArray()) {
+    //             binding = bootstrapMethod.invoke(caller, name, type, info);
+    //         } else {
+    //             Object[] argv = (Object[]) info;
+    //             maybeReBoxElements(argv);
+    //             switch (argv.length) {
+    //             case 0:
+    //                 binding = bootstrapMethod.invoke(caller, name, type);
+    //                 break;
+    //             case 1:
+    //                 binding = bootstrapMethod.invoke(caller, name, type,
+    //                                                  argv[0]);
+    //                 break;
+    //             case 2:
+    //                 binding = bootstrapMethod.invoke(caller, name, type,
+    //                                                  argv[0], argv[1]);
+    //                 break;
+    //             case 3:
+    //                 binding = bootstrapMethod.invoke(caller, name, type,
+    //                                                  argv[0], argv[1], argv[2]);
+    //                 break;
+    //             case 4:
+    //                 binding = bootstrapMethod.invoke(caller, name, type,
+    //                                                  argv[0], argv[1], argv[2], argv[3]);
+    //                 break;
+    //             case 5:
+    //                 binding = bootstrapMethod.invoke(caller, name, type,
+    //                                                  argv[0], argv[1], argv[2], argv[3], argv[4]);
+    //                 break;
+    //             case 6:
+    //                 binding = bootstrapMethod.invoke(caller, name, type,
+    //                                                  argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
+    //                 break;
+    //             default:
+    //                 final int NON_SPREAD_ARG_COUNT = 3;  // (caller, name, type)
+    //                 if (NON_SPREAD_ARG_COUNT + argv.length > MethodType.MAX_MH_ARITY)
+    //                     throw new BootstrapMethodError("too many bootstrap method arguments");
+    //                 MethodType bsmType = bootstrapMethod.type();
+    //                 MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length);
+    //                 MethodHandle typedBSM = bootstrapMethod.asType(invocationType);
+    //                 MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT);
+    //                 binding = spreader.invokeExact(typedBSM, (Object)caller, (Object)name, (Object)type, argv);
+    //             }
+    //         }
+    //         //System.out.println("BSM for "+name+type+" => "+binding);
+    //         if (binding instanceof CallSite) {
+    //             site = (CallSite) binding;
+    //         }  else {
+    //             throw new ClassCastException("bootstrap method failed to produce a CallSite");
+    //         }
+    //         if (!site.getTarget().type().equals(type))
+    //             throw wrongTargetType(site.getTarget(), type);
+    //     } catch (Throwable ex) {
+    //         BootstrapMethodError bex;
+    //         if (ex instanceof BootstrapMethodError)
+    //             bex = (BootstrapMethodError) ex;
+    //         else
+    //             bex = new BootstrapMethodError("call site initialization exception", ex);
+    //         throw bex;
+    //     }
+    //     return site;
+    // }
+
+    // private static Object maybeReBox(Object x) {
+    //     if (x instanceof Integer) {
+    //         int xi = (int) x;
+    //         if (xi == (byte) xi)
+    //             x = xi;  // must rebox; see JLS 5.1.7
+    //     }
+    //     return x;
+    // }
+    // private static void maybeReBoxElements(Object[] xa) {
+    //     for (int i = 0; i < xa.length; i++) {
+    //         xa[i] = maybeReBox(xa[i]);
+    //     }
+    // }
 }
diff --git a/java/lang/invoke/MethodHandle.java b/java/lang/invoke/MethodHandle.java
index 159f9dd..af3db10 100644
--- a/java/lang/invoke/MethodHandle.java
+++ b/java/lang/invoke/MethodHandle.java
@@ -25,28 +25,1353 @@
 
 package java.lang.invoke;
 
+
+import dalvik.system.EmulatedStackFrame;
+
+import static java.lang.invoke.MethodHandleStatics.*;
+
+/**
+ * A method handle is a typed, directly executable reference to an underlying method,
+ * constructor, field, or similar low-level operation, with optional
+ * transformations of arguments or return values.
+ * These transformations are quite general, and include such patterns as
+ * {@linkplain #asType conversion},
+ * {@linkplain #bindTo insertion},
+ * {@linkplain java.lang.invoke.MethodHandles#dropArguments deletion},
+ * and {@linkplain java.lang.invoke.MethodHandles#filterArguments substitution}.
+ *
+ * <h1>Method handle contents</h1>
+ * Method handles are dynamically and strongly typed according to their parameter and return types.
+ * They are not distinguished by the name or the defining class of their underlying methods.
+ * A method handle must be invoked using a symbolic type descriptor which matches
+ * the method handle's own {@linkplain #type type descriptor}.
+ * <p>
+ * Every method handle reports its type descriptor via the {@link #type type} accessor.
+ * This type descriptor is a {@link java.lang.invoke.MethodType MethodType} object,
+ * whose structure is a series of classes, one of which is
+ * the return type of the method (or {@code void.class} if none).
+ * <p>
+ * A method handle's type controls the types of invocations it accepts,
+ * and the kinds of transformations that apply to it.
+ * <p>
+ * A method handle contains a pair of special invoker methods
+ * called {@link #invokeExact invokeExact} and {@link #invoke invoke}.
+ * Both invoker methods provide direct access to the method handle's
+ * underlying method, constructor, field, or other operation,
+ * as modified by transformations of arguments and return values.
+ * Both invokers accept calls which exactly match the method handle's own type.
+ * The plain, inexact invoker also accepts a range of other call types.
+ * <p>
+ * Method handles are immutable and have no visible state.
+ * Of course, they can be bound to underlying methods or data which exhibit state.
+ * With respect to the Java Memory Model, any method handle will behave
+ * as if all of its (internal) fields are final variables.  This means that any method
+ * handle made visible to the application will always be fully formed.
+ * This is true even if the method handle is published through a shared
+ * variable in a data race.
+ * <p>
+ * Method handles cannot be subclassed by the user.
+ * Implementations may (or may not) create internal subclasses of {@code MethodHandle}
+ * which may be visible via the {@link java.lang.Object#getClass Object.getClass}
+ * operation.  The programmer should not draw conclusions about a method handle
+ * from its specific class, as the method handle class hierarchy (if any)
+ * may change from time to time or across implementations from different vendors.
+ *
+ * <h1>Method handle compilation</h1>
+ * A Java method call expression naming {@code invokeExact} or {@code invoke}
+ * can invoke a method handle from Java source code.
+ * From the viewpoint of source code, these methods can take any arguments
+ * and their result can be cast to any return type.
+ * Formally this is accomplished by giving the invoker methods
+ * {@code Object} return types and variable arity {@code Object} arguments,
+ * but they have an additional quality called <em>signature polymorphism</em>
+ * which connects this freedom of invocation directly to the JVM execution stack.
+ * <p>
+ * As is usual with virtual methods, source-level calls to {@code invokeExact}
+ * and {@code invoke} compile to an {@code invokevirtual} instruction.
+ * More unusually, the compiler must record the actual argument types,
+ * and may not perform method invocation conversions on the arguments.
+ * Instead, it must push them on the stack according to their own unconverted types.
+ * The method handle object itself is pushed on the stack before the arguments.
+ * The compiler then calls the method handle with a symbolic type descriptor which
+ * describes the argument and return types.
+ * <p>
+ * To issue a complete symbolic type descriptor, the compiler must also determine
+ * the return type.  This is based on a cast on the method invocation expression,
+ * if there is one, or else {@code Object} if the invocation is an expression
+ * or else {@code void} if the invocation is a statement.
+ * The cast may be to a primitive type (but not {@code void}).
+ * <p>
+ * As a corner case, an uncasted {@code null} argument is given
+ * a symbolic type descriptor of {@code java.lang.Void}.
+ * The ambiguity with the type {@code Void} is harmless, since there are no references of type
+ * {@code Void} except the null reference.
+ *
+ * <h1>Method handle invocation</h1>
+ * The first time a {@code invokevirtual} instruction is executed
+ * it is linked, by symbolically resolving the names in the instruction
+ * and verifying that the method call is statically legal.
+ * This is true of calls to {@code invokeExact} and {@code invoke}.
+ * In this case, the symbolic type descriptor emitted by the compiler is checked for
+ * correct syntax and names it contains are resolved.
+ * Thus, an {@code invokevirtual} instruction which invokes
+ * a method handle will always link, as long
+ * as the symbolic type descriptor is syntactically well-formed
+ * and the types exist.
+ * <p>
+ * When the {@code invokevirtual} is executed after linking,
+ * the receiving method handle's type is first checked by the JVM
+ * to ensure that it matches the symbolic type descriptor.
+ * If the type match fails, it means that the method which the
+ * caller is invoking is not present on the individual
+ * method handle being invoked.
+ * <p>
+ * In the case of {@code invokeExact}, the type descriptor of the invocation
+ * (after resolving symbolic type names) must exactly match the method type
+ * of the receiving method handle.
+ * In the case of plain, inexact {@code invoke}, the resolved type descriptor
+ * must be a valid argument to the receiver's {@link #asType asType} method.
+ * Thus, plain {@code invoke} is more permissive than {@code invokeExact}.
+ * <p>
+ * After type matching, a call to {@code invokeExact} directly
+ * and immediately invoke the method handle's underlying method
+ * (or other behavior, as the case may be).
+ * <p>
+ * A call to plain {@code invoke} works the same as a call to
+ * {@code invokeExact}, if the symbolic type descriptor specified by the caller
+ * exactly matches the method handle's own type.
+ * If there is a type mismatch, {@code invoke} attempts
+ * to adjust the type of the receiving method handle,
+ * as if by a call to {@link #asType asType},
+ * to obtain an exactly invokable method handle {@code M2}.
+ * This allows a more powerful negotiation of method type
+ * between caller and callee.
+ * <p>
+ * (<em>Note:</em> The adjusted method handle {@code M2} is not directly observable,
+ * and implementations are therefore not required to materialize it.)
+ *
+ * <h1>Invocation checking</h1>
+ * In typical programs, method handle type matching will usually succeed.
+ * But if a match fails, the JVM will throw a {@link WrongMethodTypeException},
+ * either directly (in the case of {@code invokeExact}) or indirectly as if
+ * by a failed call to {@code asType} (in the case of {@code invoke}).
+ * <p>
+ * Thus, a method type mismatch which might show up as a linkage error
+ * in a statically typed program can show up as
+ * a dynamic {@code WrongMethodTypeException}
+ * in a program which uses method handles.
+ * <p>
+ * Because method types contain "live" {@code Class} objects,
+ * method type matching takes into account both types names and class loaders.
+ * Thus, even if a method handle {@code M} is created in one
+ * class loader {@code L1} and used in another {@code L2},
+ * method handle calls are type-safe, because the caller's symbolic type
+ * descriptor, as resolved in {@code L2},
+ * is matched against the original callee method's symbolic type descriptor,
+ * as resolved in {@code L1}.
+ * The resolution in {@code L1} happens when {@code M} is created
+ * and its type is assigned, while the resolution in {@code L2} happens
+ * when the {@code invokevirtual} instruction is linked.
+ * <p>
+ * Apart from the checking of type descriptors,
+ * a method handle's capability to call its underlying method is unrestricted.
+ * If a method handle is formed on a non-public method by a class
+ * that has access to that method, the resulting handle can be used
+ * in any place by any caller who receives a reference to it.
+ * <p>
+ * Unlike with the Core Reflection API, where access is checked every time
+ * a reflective method is invoked,
+ * method handle access checking is performed
+ * <a href="MethodHandles.Lookup.html#access">when the method handle is created</a>.
+ * In the case of {@code ldc} (see below), access checking is performed as part of linking
+ * the constant pool entry underlying the constant method handle.
+ * <p>
+ * Thus, handles to non-public methods, or to methods in non-public classes,
+ * should generally be kept secret.
+ * They should not be passed to untrusted code unless their use from
+ * the untrusted code would be harmless.
+ *
+ * <h1>Method handle creation</h1>
+ * Java code can create a method handle that directly accesses
+ * any method, constructor, or field that is accessible to that code.
+ * This is done via a reflective, capability-based API called
+ * {@link java.lang.invoke.MethodHandles.Lookup MethodHandles.Lookup}
+ * For example, a static method handle can be obtained
+ * from {@link java.lang.invoke.MethodHandles.Lookup#findStatic Lookup.findStatic}.
+ * There are also conversion methods from Core Reflection API objects,
+ * such as {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}.
+ * <p>
+ * Like classes and strings, method handles that correspond to accessible
+ * fields, methods, and constructors can also be represented directly
+ * in a class file's constant pool as constants to be loaded by {@code ldc} bytecodes.
+ * A new type of constant pool entry, {@code CONSTANT_MethodHandle},
+ * refers directly to an associated {@code CONSTANT_Methodref},
+ * {@code CONSTANT_InterfaceMethodref}, or {@code CONSTANT_Fieldref}
+ * constant pool entry.
+ * (For full details on method handle constants,
+ * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
+ * <p>
+ * Method handles produced by lookups or constant loads from methods or
+ * constructors with the variable arity modifier bit ({@code 0x0080})
+ * have a corresponding variable arity, as if they were defined with
+ * the help of {@link #asVarargsCollector asVarargsCollector}.
+ * <p>
+ * A method reference may refer either to a static or non-static method.
+ * In the non-static case, the method handle type includes an explicit
+ * receiver argument, prepended before any other arguments.
+ * In the method handle's type, the initial receiver argument is typed
+ * according to the class under which the method was initially requested.
+ * (E.g., if a non-static method handle is obtained via {@code ldc},
+ * the type of the receiver is the class named in the constant pool entry.)
+ * <p>
+ * Method handle constants are subject to the same link-time access checks
+ * their corresponding bytecode instructions, and the {@code ldc} instruction
+ * will throw corresponding linkage errors if the bytecode behaviors would
+ * throw such errors.
+ * <p>
+ * As a corollary of this, access to protected members is restricted
+ * to receivers only of the accessing class, or one of its subclasses,
+ * and the accessing class must in turn be a subclass (or package sibling)
+ * of the protected member's defining class.
+ * If a method reference refers to a protected non-static method or field
+ * of a class outside the current package, the receiver argument will
+ * be narrowed to the type of the accessing class.
+ * <p>
+ * When a method handle to a virtual method is invoked, the method is
+ * always looked up in the receiver (that is, the first argument).
+ * <p>
+ * A non-virtual method handle to a specific virtual method implementation
+ * can also be created.  These do not perform virtual lookup based on
+ * receiver type.  Such a method handle simulates the effect of
+ * an {@code invokespecial} instruction to the same method.
+ *
+ * <h1>Usage examples</h1>
+ * Here are some examples of usage:
+ * <blockquote><pre>{@code
+Object x, y; String s; int i;
+MethodType mt; MethodHandle mh;
+MethodHandles.Lookup lookup = MethodHandles.lookup();
+// mt is (char,char)String
+mt = MethodType.methodType(String.class, char.class, char.class);
+mh = lookup.findVirtual(String.class, "replace", mt);
+s = (String) mh.invokeExact("daddy",'d','n');
+// invokeExact(Ljava/lang/String;CC)Ljava/lang/String;
+assertEquals(s, "nanny");
+// weakly typed invocation (using MHs.invoke)
+s = (String) mh.invokeWithArguments("sappy", 'p', 'v');
+assertEquals(s, "savvy");
+// mt is (Object[])List
+mt = MethodType.methodType(java.util.List.class, Object[].class);
+mh = lookup.findStatic(java.util.Arrays.class, "asList", mt);
+assert(mh.isVarargsCollector());
+x = mh.invoke("one", "two");
+// invoke(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
+assertEquals(x, java.util.Arrays.asList("one","two"));
+// mt is (Object,Object,Object)Object
+mt = MethodType.genericMethodType(3);
+mh = mh.asType(mt);
+x = mh.invokeExact((Object)1, (Object)2, (Object)3);
+// invokeExact(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+assertEquals(x, java.util.Arrays.asList(1,2,3));
+// mt is ()int
+mt = MethodType.methodType(int.class);
+mh = lookup.findVirtual(java.util.List.class, "size", mt);
+i = (int) mh.invokeExact(java.util.Arrays.asList(1,2,3));
+// invokeExact(Ljava/util/List;)I
+assert(i == 3);
+mt = MethodType.methodType(void.class, String.class);
+mh = lookup.findVirtual(java.io.PrintStream.class, "println", mt);
+mh.invokeExact(System.out, "Hello, world.");
+// invokeExact(Ljava/io/PrintStream;Ljava/lang/String;)V
+ * }</pre></blockquote>
+ * Each of the above calls to {@code invokeExact} or plain {@code invoke}
+ * generates a single invokevirtual instruction with
+ * the symbolic type descriptor indicated in the following comment.
+ * In these examples, the helper method {@code assertEquals} is assumed to
+ * be a method which calls {@link java.util.Objects#equals(Object,Object) Objects.equals}
+ * on its arguments, and asserts that the result is true.
+ *
+ * <h1>Exceptions</h1>
+ * The methods {@code invokeExact} and {@code invoke} are declared
+ * to throw {@link java.lang.Throwable Throwable},
+ * which is to say that there is no static restriction on what a method handle
+ * can throw.  Since the JVM does not distinguish between checked
+ * and unchecked exceptions (other than by their class, of course),
+ * there is no particular effect on bytecode shape from ascribing
+ * checked exceptions to method handle invocations.  But in Java source
+ * code, methods which perform method handle calls must either explicitly
+ * throw {@code Throwable}, or else must catch all
+ * throwables locally, rethrowing only those which are legal in the context,
+ * and wrapping ones which are illegal.
+ *
+ * <h1><a name="sigpoly"></a>Signature polymorphism</h1>
+ * The unusual compilation and linkage behavior of
+ * {@code invokeExact} and plain {@code invoke}
+ * is referenced by the term <em>signature polymorphism</em>.
+ * As defined in the Java Language Specification,
+ * a signature polymorphic method is one which can operate with
+ * any of a wide range of call signatures and return types.
+ * <p>
+ * In source code, a call to a signature polymorphic method will
+ * compile, regardless of the requested symbolic type descriptor.
+ * As usual, the Java compiler emits an {@code invokevirtual}
+ * instruction with the given symbolic type descriptor against the named method.
+ * The unusual part is that the symbolic type descriptor is derived from
+ * the actual argument and return types, not from the method declaration.
+ * <p>
+ * When the JVM processes bytecode containing signature polymorphic calls,
+ * it will successfully link any such call, regardless of its symbolic type descriptor.
+ * (In order to retain type safety, the JVM will guard such calls with suitable
+ * dynamic type checks, as described elsewhere.)
+ * <p>
+ * Bytecode generators, including the compiler back end, are required to emit
+ * untransformed symbolic type descriptors for these methods.
+ * Tools which determine symbolic linkage are required to accept such
+ * untransformed descriptors, without reporting linkage errors.
+ *
+ * <h1>Interoperation between method handles and the Core Reflection API</h1>
+ * Using factory methods in the {@link java.lang.invoke.MethodHandles.Lookup Lookup} API,
+ * any class member represented by a Core Reflection API object
+ * can be converted to a behaviorally equivalent method handle.
+ * For example, a reflective {@link java.lang.reflect.Method Method} can
+ * be converted to a method handle using
+ * {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect}.
+ * The resulting method handles generally provide more direct and efficient
+ * access to the underlying class members.
+ * <p>
+ * As a special case,
+ * when the Core Reflection API is used to view the signature polymorphic
+ * methods {@code invokeExact} or plain {@code invoke} in this class,
+ * they appear as ordinary non-polymorphic methods.
+ * Their reflective appearance, as viewed by
+ * {@link java.lang.Class#getDeclaredMethod Class.getDeclaredMethod},
+ * is unaffected by their special status in this API.
+ * For example, {@link java.lang.reflect.Method#getModifiers Method.getModifiers}
+ * will report exactly those modifier bits required for any similarly
+ * declared method, including in this case {@code native} and {@code varargs} bits.
+ * <p>
+ * As with any reflected method, these methods (when reflected) may be
+ * invoked via {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.
+ * However, such reflective calls do not result in method handle invocations.
+ * Such a call, if passed the required argument
+ * (a single one, of type {@code Object[]}), will ignore the argument and
+ * will throw an {@code UnsupportedOperationException}.
+ * <p>
+ * Since {@code invokevirtual} instructions can natively
+ * invoke method handles under any symbolic type descriptor, this reflective view conflicts
+ * with the normal presentation of these methods via bytecodes.
+ * Thus, these two native methods, when reflectively viewed by
+ * {@code Class.getDeclaredMethod}, may be regarded as placeholders only.
+ * <p>
+ * In order to obtain an invoker method for a particular type descriptor,
+ * use {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker},
+ * or {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}.
+ * The {@link java.lang.invoke.MethodHandles.Lookup#findVirtual Lookup.findVirtual}
+ * API is also able to return a method handle
+ * to call {@code invokeExact} or plain {@code invoke},
+ * for any specified type descriptor .
+ *
+ * <h1>Interoperation between method handles and Java generics</h1>
+ * A method handle can be obtained on a method, constructor, or field
+ * which is declared with Java generic types.
+ * As with the Core Reflection API, the type of the method handle
+ * will constructed from the erasure of the source-level type.
+ * When a method handle is invoked, the types of its arguments
+ * or the return value cast type may be generic types or type instances.
+ * If this occurs, the compiler will replace those
+ * types by their erasures when it constructs the symbolic type descriptor
+ * for the {@code invokevirtual} instruction.
+ * <p>
+ * Method handles do not represent
+ * their function-like types in terms of Java parameterized (generic) types,
+ * because there are three mismatches between function-like types and parameterized
+ * Java types.
+ * <ul>
+ * <li>Method types range over all possible arities,
+ * from no arguments to up to the  <a href="MethodHandle.html#maxarity">maximum number</a> of allowed arguments.
+ * Generics are not variadic, and so cannot represent this.</li>
+ * <li>Method types can specify arguments of primitive types,
+ * which Java generic types cannot range over.</li>
+ * <li>Higher order functions over method handles (combinators) are
+ * often generic across a wide range of function types, including
+ * those of multiple arities.  It is impossible to represent such
+ * genericity with a Java type parameter.</li>
+ * </ul>
+ *
+ * <h1><a name="maxarity"></a>Arity limits</h1>
+ * The JVM imposes on all methods and constructors of any kind an absolute
+ * limit of 255 stacked arguments.  This limit can appear more restrictive
+ * in certain cases:
+ * <ul>
+ * <li>A {@code long} or {@code double} argument counts (for purposes of arity limits) as two argument slots.
+ * <li>A non-static method consumes an extra argument for the object on which the method is called.
+ * <li>A constructor consumes an extra argument for the object which is being constructed.
+ * <li>Since a method handle&rsquo;s {@code invoke} method (or other signature-polymorphic method) is non-virtual,
+ *     it consumes an extra argument for the method handle itself, in addition to any non-virtual receiver object.
+ * </ul>
+ * These limits imply that certain method handles cannot be created, solely because of the JVM limit on stacked arguments.
+ * For example, if a static JVM method accepts exactly 255 arguments, a method handle cannot be created for it.
+ * Attempts to create method handles with impossible method types lead to an {@link IllegalArgumentException}.
+ * In particular, a method handle&rsquo;s type must not have an arity of the exact maximum 255.
+ *
+ * @see MethodType
+ * @see MethodHandles
+ * @author John Rose, JSR 292 EG
+ */
 public abstract class MethodHandle {
+    // Android-changed:
+    //
+    // static { MethodHandleImpl.initStatics(); }
+    //
+    // LambdaForm and customizationCount are currently unused in our implementation
+    // and will be substituted with appropriate implementation / delegate classes.
+    //
+    // /*private*/ final LambdaForm form;
+    // form is not private so that invokers can easily fetch it
+    // /*non-public*/ byte customizationCount;
+    // customizationCount should be accessible from invokers
 
-    public MethodType type() { return null; }
 
-    public final Object invokeExact(Object... args) throws Throwable { return null; }
+    /**
+     * Internal marker interface which distinguishes (to the Java compiler)
+     * those methods which are <a href="MethodHandle.html#sigpoly">signature polymorphic</a>.
+     *
+     * @hide
+     */
+    @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+    public @interface PolymorphicSignature { }
 
-    public final Object invoke(Object... args) throws Throwable { return null; }
+    /**
+     * The type of this method handle, this corresponds to the exact type of the method
+     * being invoked.
+     */
+    private final MethodType type;
 
-    public Object invokeWithArguments(Object... arguments) throws Throwable { return null; }
+    /**
+     * The nominal type of this method handle, will be non-null if a method handle declares
+     * a different type from its "real" type, which is either the type of the method being invoked
+     * or the type of the emulated stackframe expected by an underyling adapter.
+     */
+    private MethodType nominalType;
 
-    public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable { return null; }
+    /**
+     * The spread invoker associated with this type with zero trailing arguments.
+     * This is used to speed up invokeWithArguments.
+     */
+    private MethodHandle cachedSpreadInvoker;
 
-    public MethodHandle asType(MethodType newType) { return null; }
+    /**
+     * The INVOKE* constants and SGET/SPUT and IGET/IPUT constants specify the behaviour of this
+     * method handle with respect to the ArtField* or the ArtMethod* that it operates on. These
+     * behaviours are equivalent to the dex bytecode behaviour on the respective method_id or
+     * field_id in the equivalent instruction.
+     *
+     * INVOKE_TRANSFORM is a special type of handle which doesn't encode any dex bytecode behaviour,
+     * instead it transforms the list of input arguments or performs other higher order operations
+     * before (optionally) delegating to another method handle.
+     *
+     * INVOKE_CALLSITE_TRANSFORM is a variation on INVOKE_TRANSFORM where the method type of
+     * a MethodHandle dynamically varies based on the callsite. This is used by
+     * the VarargsCollector implementation which places any number of trailing arguments
+     * into an array before invoking an arity method. The "any number of trailing arguments" means
+     * it would otherwise generate WrongMethodTypeExceptions as the callsite method type and
+     * VarargsCollector method type appear incompatible.
+     */
 
-    public MethodHandle asCollector(Class<?> arrayType, int arrayLength) { return null; }
+    /** @hide */ public static final int INVOKE_VIRTUAL = 0;
+    /** @hide */ public static final int INVOKE_SUPER = 1;
+    /** @hide */ public static final int INVOKE_DIRECT = 2;
+    /** @hide */ public static final int INVOKE_STATIC = 3;
+    /** @hide */ public static final int INVOKE_INTERFACE = 4;
+    /** @hide */ public static final int INVOKE_TRANSFORM = 5;
+    /** @hide */ public static final int INVOKE_CALLSITE_TRANSFORM = 6;
+    /** @hide */ public static final int IGET = 7;
+    /** @hide */ public static final int IPUT = 8;
+    /** @hide */ public static final int SGET = 9;
+    /** @hide */ public static final int SPUT = 10;
 
-    public MethodHandle asVarargsCollector(Class<?> arrayType) { return null; }
+    // The kind of this method handle (used by the runtime). This is one of the INVOKE_*
+    // constants or SGET/SPUT, IGET/IPUT.
+    /** @hide */ protected final int handleKind;
 
-    public boolean isVarargsCollector() { return false; }
+    // The ArtMethod* or ArtField* associated with this method handle (used by the runtime).
+    /** @hide */ protected final long artFieldOrMethod;
 
-    public MethodHandle asFixedArity() { return null; }
+    /** @hide */
+    protected MethodHandle(long artFieldOrMethod, int handleKind, MethodType type) {
+        this.artFieldOrMethod = artFieldOrMethod;
+        this.handleKind = handleKind;
+        this.type = type;
+    }
 
-    public MethodHandle bindTo(Object x) { return null; }
+    /**
+     * Reports the type of this method handle.
+     * Every invocation of this method handle via {@code invokeExact} must exactly match this type.
+     * @return the method handle type
+     */
+    public MethodType type() {
+        if (nominalType != null) {
+            return nominalType;
+        }
 
+        return type;
+    }
+
+    /**
+     * Invokes the method handle, allowing any caller type descriptor, but requiring an exact type match.
+     * The symbolic type descriptor at the call site of {@code invokeExact} must
+     * exactly match this method handle's {@link #type type}.
+     * No conversions are allowed on arguments or return values.
+     * <p>
+     * When this method is observed via the Core Reflection API,
+     * it will appear as a single native method, taking an object array and returning an object.
+     * If this native method is invoked directly via
+     * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
+     * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
+     * it will throw an {@code UnsupportedOperationException}.
+     * @param args the signature-polymorphic parameter list, statically represented using varargs
+     * @return the signature-polymorphic result, statically represented using {@code Object}
+     * @throws WrongMethodTypeException if the target's type is not identical with the caller's symbolic type descriptor
+     * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
+     */
+    public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable;
+
+    /**
+     * Invokes the method handle, allowing any caller type descriptor,
+     * and optionally performing conversions on arguments and return values.
+     * <p>
+     * If the call site's symbolic type descriptor exactly matches this method handle's {@link #type type},
+     * the call proceeds as if by {@link #invokeExact invokeExact}.
+     * <p>
+     * Otherwise, the call proceeds as if this method handle were first
+     * adjusted by calling {@link #asType asType} to adjust this method handle
+     * to the required type, and then the call proceeds as if by
+     * {@link #invokeExact invokeExact} on the adjusted method handle.
+     * <p>
+     * There is no guarantee that the {@code asType} call is actually made.
+     * If the JVM can predict the results of making the call, it may perform
+     * adaptations directly on the caller's arguments,
+     * and call the target method handle according to its own exact type.
+     * <p>
+     * The resolved type descriptor at the call site of {@code invoke} must
+     * be a valid argument to the receivers {@code asType} method.
+     * In particular, the caller must specify the same argument arity
+     * as the callee's type,
+     * if the callee is not a {@linkplain #asVarargsCollector variable arity collector}.
+     * <p>
+     * When this method is observed via the Core Reflection API,
+     * it will appear as a single native method, taking an object array and returning an object.
+     * If this native method is invoked directly via
+     * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}, via JNI,
+     * or indirectly via {@link java.lang.invoke.MethodHandles.Lookup#unreflect Lookup.unreflect},
+     * it will throw an {@code UnsupportedOperationException}.
+     * @param args the signature-polymorphic parameter list, statically represented using varargs
+     * @return the signature-polymorphic result, statically represented using {@code Object}
+     * @throws WrongMethodTypeException if the target's type cannot be adjusted to the caller's symbolic type descriptor
+     * @throws ClassCastException if the target's type can be adjusted to the caller, but a reference cast fails
+     * @throws Throwable anything thrown by the underlying method propagates unchanged through the method handle call
+     */
+    public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;
+
+    // Android-changed: Removed implementation details.
+    //
+    // /*non-public*/ final native @PolymorphicSignature Object invokeBasic(Object... args)
+    // /*non-public*/ static native @PolymorphicSignature Object linkToVirtual(Object... args)
+    // /*non-public*/ static native @PolymorphicSignature Object linkToStatic(Object... args)
+    // /*non-public*/ static native @PolymorphicSignature Object linkToSpecial(Object... args)
+    // /*non-public*/ static native @PolymorphicSignature Object linkToInterface(Object... args)
+
+    /**
+     * Performs a variable arity invocation, passing the arguments in the given list
+     * to the method handle, as if via an inexact {@link #invoke invoke} from a call site
+     * which mentions only the type {@code Object}, and whose arity is the length
+     * of the argument list.
+     * <p>
+     * Specifically, execution proceeds as if by the following steps,
+     * although the methods are not guaranteed to be called if the JVM
+     * can predict their effects.
+     * <ul>
+     * <li>Determine the length of the argument array as {@code N}.
+     *     For a null reference, {@code N=0}. </li>
+     * <li>Determine the general type {@code TN} of {@code N} arguments as
+     *     as {@code TN=MethodType.genericMethodType(N)}.</li>
+     * <li>Force the original target method handle {@code MH0} to the
+     *     required type, as {@code MH1 = MH0.asType(TN)}. </li>
+     * <li>Spread the array into {@code N} separate arguments {@code A0, ...}. </li>
+     * <li>Invoke the type-adjusted method handle on the unpacked arguments:
+     *     MH1.invokeExact(A0, ...). </li>
+     * <li>Take the return value as an {@code Object} reference. </li>
+     * </ul>
+     * <p>
+     * Because of the action of the {@code asType} step, the following argument
+     * conversions are applied as necessary:
+     * <ul>
+     * <li>reference casting
+     * <li>unboxing
+     * <li>widening primitive conversions
+     * </ul>
+     * <p>
+     * The result returned by the call is boxed if it is a primitive,
+     * or forced to null if the return type is void.
+     * <p>
+     * This call is equivalent to the following code:
+     * <blockquote><pre>{@code
+     * MethodHandle invoker = MethodHandles.spreadInvoker(this.type(), 0);
+     * Object result = invoker.invokeExact(this, arguments);
+     * }</pre></blockquote>
+     * <p>
+     * Unlike the signature polymorphic methods {@code invokeExact} and {@code invoke},
+     * {@code invokeWithArguments} can be accessed normally via the Core Reflection API and JNI.
+     * It can therefore be used as a bridge between native or reflective code and method handles.
+     *
+     * @param arguments the arguments to pass to the target
+     * @return the result returned by the target
+     * @throws ClassCastException if an argument cannot be converted by reference casting
+     * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments
+     * @throws Throwable anything thrown by the target method invocation
+     * @see MethodHandles#spreadInvoker
+     */
+    public Object invokeWithArguments(Object... arguments) throws Throwable {
+        MethodHandle invoker = null;
+        synchronized (this) {
+            if (cachedSpreadInvoker == null) {
+                cachedSpreadInvoker = MethodHandles.spreadInvoker(this.type(), 0);
+            }
+
+            invoker = cachedSpreadInvoker;
+        }
+
+        return invoker.invoke(this, arguments);
+    }
+
+    /**
+     * Performs a variable arity invocation, passing the arguments in the given array
+     * to the method handle, as if via an inexact {@link #invoke invoke} from a call site
+     * which mentions only the type {@code Object}, and whose arity is the length
+     * of the argument array.
+     * <p>
+     * This method is also equivalent to the following code:
+     * <blockquote><pre>{@code
+     *   invokeWithArguments(arguments.toArray()
+     * }</pre></blockquote>
+     *
+     * @param arguments the arguments to pass to the target
+     * @return the result returned by the target
+     * @throws NullPointerException if {@code arguments} is a null reference
+     * @throws ClassCastException if an argument cannot be converted by reference casting
+     * @throws WrongMethodTypeException if the target's type cannot be adjusted to take the given number of {@code Object} arguments
+     * @throws Throwable anything thrown by the target method invocation
+     */
+    public Object invokeWithArguments(java.util.List<?> arguments) throws Throwable {
+        return invokeWithArguments(arguments.toArray());
+    }
+
+    /**
+     * Produces an adapter method handle which adapts the type of the
+     * current method handle to a new type.
+     * The resulting method handle is guaranteed to report a type
+     * which is equal to the desired new type.
+     * <p>
+     * If the original type and new type are equal, returns {@code this}.
+     * <p>
+     * The new method handle, when invoked, will perform the following
+     * steps:
+     * <ul>
+     * <li>Convert the incoming argument list to match the original
+     *     method handle's argument list.
+     * <li>Invoke the original method handle on the converted argument list.
+     * <li>Convert any result returned by the original method handle
+     *     to the return type of new method handle.
+     * </ul>
+     * <p>
+     * This method provides the crucial behavioral difference between
+     * {@link #invokeExact invokeExact} and plain, inexact {@link #invoke invoke}.
+     * The two methods
+     * perform the same steps when the caller's type descriptor exactly m atches
+     * the callee's, but when the types differ, plain {@link #invoke invoke}
+     * also calls {@code asType} (or some internal equivalent) in order
+     * to match up the caller's and callee's types.
+     * <p>
+     * If the current method is a variable arity method handle
+     * argument list conversion may involve the conversion and collection
+     * of several arguments into an array, as
+     * {@linkplain #asVarargsCollector described elsewhere}.
+     * In every other case, all conversions are applied <em>pairwise</em>,
+     * which means that each argument or return value is converted to
+     * exactly one argument or return value (or no return value).
+     * The applied conversions are defined by consulting the
+     * the corresponding component types of the old and new
+     * method handle types.
+     * <p>
+     * Let <em>T0</em> and <em>T1</em> be corresponding new and old parameter types,
+     * or old and new return types.  Specifically, for some valid index {@code i}, let
+     * <em>T0</em>{@code =newType.parameterType(i)} and <em>T1</em>{@code =this.type().parameterType(i)}.
+     * Or else, going the other way for return values, let
+     * <em>T0</em>{@code =this.type().returnType()} and <em>T1</em>{@code =newType.returnType()}.
+     * If the types are the same, the new method handle makes no change
+     * to the corresponding argument or return value (if any).
+     * Otherwise, one of the following conversions is applied
+     * if possible:
+     * <ul>
+     * <li>If <em>T0</em> and <em>T1</em> are references, then a cast to <em>T1</em> is applied.
+     *     (The types do not need to be related in any particular way.
+     *     This is because a dynamic value of null can convert to any reference type.)
+     * <li>If <em>T0</em> and <em>T1</em> are primitives, then a Java method invocation
+     *     conversion (JLS 5.3) is applied, if one exists.
+     *     (Specifically, <em>T0</em> must convert to <em>T1</em> by a widening primitive conversion.)
+     * <li>If <em>T0</em> is a primitive and <em>T1</em> a reference,
+     *     a Java casting conversion (JLS 5.5) is applied if one exists.
+     *     (Specifically, the value is boxed from <em>T0</em> to its wrapper class,
+     *     which is then widened as needed to <em>T1</em>.)
+     * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing
+     *     conversion will be applied at runtime, possibly followed
+     *     by a Java method invocation conversion (JLS 5.3)
+     *     on the primitive value.  (These are the primitive widening conversions.)
+     *     <em>T0</em> must be a wrapper class or a supertype of one.
+     *     (In the case where <em>T0</em> is Object, these are the conversions
+     *     allowed by {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}.)
+     *     The unboxing conversion must have a possibility of success, which means that
+     *     if <em>T0</em> is not itself a wrapper class, there must exist at least one
+     *     wrapper class <em>TW</em> which is a subtype of <em>T0</em> and whose unboxed
+     *     primitive value can be widened to <em>T1</em>.
+     * <li>If the return type <em>T1</em> is marked as void, any returned value is discarded
+     * <li>If the return type <em>T0</em> is void and <em>T1</em> a reference, a null value is introduced.
+     * <li>If the return type <em>T0</em> is void and <em>T1</em> a primitive,
+     *     a zero value is introduced.
+     * </ul>
+     * (<em>Note:</em> Both <em>T0</em> and <em>T1</em> may be regarded as static types,
+     * because neither corresponds specifically to the <em>dynamic type</em> of any
+     * actual argument or return value.)
+     * <p>
+     * The method handle conversion cannot be made if any one of the required
+     * pairwise conversions cannot be made.
+     * <p>
+     * At runtime, the conversions applied to reference arguments
+     * or return values may require additional runtime checks which can fail.
+     * An unboxing operation may fail because the original reference is null,
+     * causing a {@link java.lang.NullPointerException NullPointerException}.
+     * An unboxing operation or a reference cast may also fail on a reference
+     * to an object of the wrong type,
+     * causing a {@link java.lang.ClassCastException ClassCastException}.
+     * Although an unboxing operation may accept several kinds of wrappers,
+     * if none are available, a {@code ClassCastException} will be thrown.
+     *
+     * @param newType the expected type of the new method handle
+     * @return a method handle which delegates to {@code this} after performing
+     *           any necessary argument conversions, and arranges for any
+     *           necessary return value conversions
+     * @throws NullPointerException if {@code newType} is a null reference
+     * @throws WrongMethodTypeException if the conversion cannot be made
+     * @see MethodHandles#explicitCastArguments
+     */
+    public MethodHandle asType(MethodType newType) {
+        // Fast path alternative to a heavyweight {@code asType} call.
+        // Return 'this' if the conversion will be a no-op.
+        if (newType == type) {
+            return this;
+        }
+
+        if (!type.isConvertibleTo(newType)) {
+            throw new WrongMethodTypeException("cannot convert " + this + " to " + newType);
+        }
+
+        MethodHandle mh = duplicate();
+        mh.nominalType = newType;
+        return mh;
+    }
+
+    /**
+     * Makes an <em>array-spreading</em> method handle, which accepts a trailing array argument
+     * and spreads its elements as positional arguments.
+     * The new method handle adapts, as its <i>target</i>,
+     * the current method handle.  The type of the adapter will be
+     * the same as the type of the target, except that the final
+     * {@code arrayLength} parameters of the target's type are replaced
+     * by a single array parameter of type {@code arrayType}.
+     * <p>
+     * If the array element type differs from any of the corresponding
+     * argument types on the original target,
+     * the original target is adapted to take the array elements directly,
+     * as if by a call to {@link #asType asType}.
+     * <p>
+     * When called, the adapter replaces a trailing array argument
+     * by the array's elements, each as its own argument to the target.
+     * (The order of the arguments is preserved.)
+     * They are converted pairwise by casting and/or unboxing
+     * to the types of the trailing parameters of the target.
+     * Finally the target is called.
+     * What the target eventually returns is returned unchanged by the adapter.
+     * <p>
+     * Before calling the target, the adapter verifies that the array
+     * contains exactly enough elements to provide a correct argument count
+     * to the target method handle.
+     * (The array may also be null when zero elements are required.)
+     * <p>
+     * If, when the adapter is called, the supplied array argument does
+     * not have the correct number of elements, the adapter will throw
+     * an {@link IllegalArgumentException} instead of invoking the target.
+     * <p>
+     * Here are some simple examples of array-spreading method handles:
+     * <blockquote><pre>{@code
+MethodHandle equals = publicLookup()
+  .findVirtual(String.class, "equals", methodType(boolean.class, Object.class));
+assert( (boolean) equals.invokeExact("me", (Object)"me"));
+assert(!(boolean) equals.invokeExact("me", (Object)"thee"));
+// spread both arguments from a 2-array:
+MethodHandle eq2 = equals.asSpreader(Object[].class, 2);
+assert( (boolean) eq2.invokeExact(new Object[]{ "me", "me" }));
+assert(!(boolean) eq2.invokeExact(new Object[]{ "me", "thee" }));
+// try to spread from anything but a 2-array:
+for (int n = 0; n <= 10; n++) {
+  Object[] badArityArgs = (n == 2 ? null : new Object[n]);
+  try { assert((boolean) eq2.invokeExact(badArityArgs) && false); }
+  catch (IllegalArgumentException ex) { } // OK
+}
+// spread both arguments from a String array:
+MethodHandle eq2s = equals.asSpreader(String[].class, 2);
+assert( (boolean) eq2s.invokeExact(new String[]{ "me", "me" }));
+assert(!(boolean) eq2s.invokeExact(new String[]{ "me", "thee" }));
+// spread second arguments from a 1-array:
+MethodHandle eq1 = equals.asSpreader(Object[].class, 1);
+assert( (boolean) eq1.invokeExact("me", new Object[]{ "me" }));
+assert(!(boolean) eq1.invokeExact("me", new Object[]{ "thee" }));
+// spread no arguments from a 0-array or null:
+MethodHandle eq0 = equals.asSpreader(Object[].class, 0);
+assert( (boolean) eq0.invokeExact("me", (Object)"me", new Object[0]));
+assert(!(boolean) eq0.invokeExact("me", (Object)"thee", (Object[])null));
+// asSpreader and asCollector are approximate inverses:
+for (int n = 0; n <= 2; n++) {
+    for (Class<?> a : new Class<?>[]{Object[].class, String[].class, CharSequence[].class}) {
+        MethodHandle equals2 = equals.asSpreader(a, n).asCollector(a, n);
+        assert( (boolean) equals2.invokeWithArguments("me", "me"));
+        assert(!(boolean) equals2.invokeWithArguments("me", "thee"));
+    }
+}
+MethodHandle caToString = publicLookup()
+  .findStatic(Arrays.class, "toString", methodType(String.class, char[].class));
+assertEquals("[A, B, C]", (String) caToString.invokeExact("ABC".toCharArray()));
+MethodHandle caString3 = caToString.asCollector(char[].class, 3);
+assertEquals("[A, B, C]", (String) caString3.invokeExact('A', 'B', 'C'));
+MethodHandle caToString2 = caString3.asSpreader(char[].class, 2);
+assertEquals("[A, B, C]", (String) caToString2.invokeExact('A', "BC".toCharArray()));
+     * }</pre></blockquote>
+     * @param arrayType usually {@code Object[]}, the type of the array argument from which to extract the spread arguments
+     * @param arrayLength the number of arguments to spread from an incoming array argument
+     * @return a new method handle which spreads its final array argument,
+     *         before calling the original method handle
+     * @throws NullPointerException if {@code arrayType} is a null reference
+     * @throws IllegalArgumentException if {@code arrayType} is not an array type,
+     *         or if target does not have at least
+     *         {@code arrayLength} parameter types,
+     *         or if {@code arrayLength} is negative,
+     *         or if the resulting method handle's type would have
+     *         <a href="MethodHandle.html#maxarity">too many parameters</a>
+     * @throws WrongMethodTypeException if the implied {@code asType} call fails
+     * @see #asCollector
+     */
+    public MethodHandle asSpreader(Class<?> arrayType, int arrayLength) {
+        MethodType postSpreadType = asSpreaderChecks(arrayType, arrayLength);
+
+        final int targetParamCount = postSpreadType.parameterCount();
+        MethodType dropArrayArgs = postSpreadType.dropParameterTypes(
+                (targetParamCount - arrayLength), targetParamCount);
+        MethodType adapterType = dropArrayArgs.appendParameterTypes(arrayType);
+
+        return new Transformers.Spreader(this, adapterType, arrayLength);
+    }
+
+    /**
+     * See if {@code asSpreader} can be validly called with the given arguments.
+     * Return the type of the method handle call after spreading but before conversions.
+     */
+    private MethodType asSpreaderChecks(Class<?> arrayType, int arrayLength) {
+        spreadArrayChecks(arrayType, arrayLength);
+        int nargs = type().parameterCount();
+        if (nargs < arrayLength || arrayLength < 0)
+            throw newIllegalArgumentException("bad spread array length");
+        Class<?> arrayElement = arrayType.getComponentType();
+        MethodType mtype = type();
+        boolean match = true, fail = false;
+        for (int i = nargs - arrayLength; i < nargs; i++) {
+            Class<?> ptype = mtype.parameterType(i);
+            if (ptype != arrayElement) {
+                match = false;
+                if (!MethodType.canConvert(arrayElement, ptype)) {
+                    fail = true;
+                    break;
+                }
+            }
+        }
+        if (match)  return mtype;
+        MethodType needType = mtype.asSpreaderType(arrayType, arrayLength);
+        if (!fail)  return needType;
+        // elicit an error:
+        this.asType(needType);
+        throw newInternalError("should not return", null);
+    }
+
+    private void spreadArrayChecks(Class<?> arrayType, int arrayLength) {
+        Class<?> arrayElement = arrayType.getComponentType();
+        if (arrayElement == null)
+            throw newIllegalArgumentException("not an array type", arrayType);
+        if ((arrayLength & 0x7F) != arrayLength) {
+            if ((arrayLength & 0xFF) != arrayLength)
+                throw newIllegalArgumentException("array length is not legal", arrayLength);
+            assert(arrayLength >= 128);
+            if (arrayElement == long.class ||
+                arrayElement == double.class)
+                throw newIllegalArgumentException("array length is not legal for long[] or double[]", arrayLength);
+        }
+    }
+
+    /**
+     * Makes an <em>array-collecting</em> method handle, which accepts a given number of trailing
+     * positional arguments and collects them into an array argument.
+     * The new method handle adapts, as its <i>target</i>,
+     * the current method handle.  The type of the adapter will be
+     * the same as the type of the target, except that a single trailing
+     * parameter (usually of type {@code arrayType}) is replaced by
+     * {@code arrayLength} parameters whose type is element type of {@code arrayType}.
+     * <p>
+     * If the array type differs from the final argument type on the original target,
+     * the original target is adapted to take the array type directly,
+     * as if by a call to {@link #asType asType}.
+     * <p>
+     * When called, the adapter replaces its trailing {@code arrayLength}
+     * arguments by a single new array of type {@code arrayType}, whose elements
+     * comprise (in order) the replaced arguments.
+     * Finally the target is called.
+     * What the target eventually returns is returned unchanged by the adapter.
+     * <p>
+     * (The array may also be a shared constant when {@code arrayLength} is zero.)
+     * <p>
+     * (<em>Note:</em> The {@code arrayType} is often identical to the last
+     * parameter type of the original target.
+     * It is an explicit argument for symmetry with {@code asSpreader}, and also
+     * to allow the target to use a simple {@code Object} as its last parameter type.)
+     * <p>
+     * In order to create a collecting adapter which is not restricted to a particular
+     * number of collected arguments, use {@link #asVarargsCollector asVarargsCollector} instead.
+     * <p>
+     * Here are some examples of array-collecting method handles:
+     * <blockquote><pre>{@code
+MethodHandle deepToString = publicLookup()
+  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+assertEquals("[won]",   (String) deepToString.invokeExact(new Object[]{"won"}));
+MethodHandle ts1 = deepToString.asCollector(Object[].class, 1);
+assertEquals(methodType(String.class, Object.class), ts1.type());
+//assertEquals("[won]", (String) ts1.invokeExact(         new Object[]{"won"})); //FAIL
+assertEquals("[[won]]", (String) ts1.invokeExact((Object) new Object[]{"won"}));
+// arrayType can be a subtype of Object[]
+MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
+assertEquals(methodType(String.class, String.class, String.class), ts2.type());
+assertEquals("[two, too]", (String) ts2.invokeExact("two", "too"));
+MethodHandle ts0 = deepToString.asCollector(Object[].class, 0);
+assertEquals("[]", (String) ts0.invokeExact());
+// collectors can be nested, Lisp-style
+MethodHandle ts22 = deepToString.asCollector(Object[].class, 3).asCollector(String[].class, 2);
+assertEquals("[A, B, [C, D]]", ((String) ts22.invokeExact((Object)'A', (Object)"B", "C", "D")));
+// arrayType can be any primitive array type
+MethodHandle bytesToString = publicLookup()
+  .findStatic(Arrays.class, "toString", methodType(String.class, byte[].class))
+  .asCollector(byte[].class, 3);
+assertEquals("[1, 2, 3]", (String) bytesToString.invokeExact((byte)1, (byte)2, (byte)3));
+MethodHandle longsToString = publicLookup()
+  .findStatic(Arrays.class, "toString", methodType(String.class, long[].class))
+  .asCollector(long[].class, 1);
+assertEquals("[123]", (String) longsToString.invokeExact((long)123));
+     * }</pre></blockquote>
+     * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments
+     * @param arrayLength the number of arguments to collect into a new array argument
+     * @return a new method handle which collects some trailing argument
+     *         into an array, before calling the original method handle
+     * @throws NullPointerException if {@code arrayType} is a null reference
+     * @throws IllegalArgumentException if {@code arrayType} is not an array type
+     *         or {@code arrayType} is not assignable to this method handle's trailing parameter type,
+     *         or {@code arrayLength} is not a legal array size,
+     *         or the resulting method handle's type would have
+     *         <a href="MethodHandle.html#maxarity">too many parameters</a>
+     * @throws WrongMethodTypeException if the implied {@code asType} call fails
+     * @see #asSpreader
+     * @see #asVarargsCollector
+     */
+    public MethodHandle asCollector(Class<?> arrayType, int arrayLength) {
+        asCollectorChecks(arrayType, arrayLength);
+
+        return new Transformers.Collector(this, arrayType, arrayLength);
+    }
+
+    /**
+     * See if {@code asCollector} can be validly called with the given arguments.
+     * Return false if the last parameter is not an exact match to arrayType.
+     */
+    /*non-public*/ boolean asCollectorChecks(Class<?> arrayType, int arrayLength) {
+        spreadArrayChecks(arrayType, arrayLength);
+        int nargs = type().parameterCount();
+        if (nargs != 0) {
+            Class<?> lastParam = type().parameterType(nargs-1);
+            if (lastParam == arrayType)  return true;
+            if (lastParam.isAssignableFrom(arrayType))  return false;
+        }
+        throw newIllegalArgumentException("array type not assignable to trailing argument", this, arrayType);
+    }
+
+    /**
+     * Makes a <em>variable arity</em> adapter which is able to accept
+     * any number of trailing positional arguments and collect them
+     * into an array argument.
+     * <p>
+     * The type and behavior of the adapter will be the same as
+     * the type and behavior of the target, except that certain
+     * {@code invoke} and {@code asType} requests can lead to
+     * trailing positional arguments being collected into target's
+     * trailing parameter.
+     * Also, the last parameter type of the adapter will be
+     * {@code arrayType}, even if the target has a different
+     * last parameter type.
+     * <p>
+     * This transformation may return {@code this} if the method handle is
+     * already of variable arity and its trailing parameter type
+     * is identical to {@code arrayType}.
+     * <p>
+     * When called with {@link #invokeExact invokeExact}, the adapter invokes
+     * the target with no argument changes.
+     * (<em>Note:</em> This behavior is different from a
+     * {@linkplain #asCollector fixed arity collector},
+     * since it accepts a whole array of indeterminate length,
+     * rather than a fixed number of arguments.)
+     * <p>
+     * When called with plain, inexact {@link #invoke invoke}, if the caller
+     * type is the same as the adapter, the adapter invokes the target as with
+     * {@code invokeExact}.
+     * (This is the normal behavior for {@code invoke} when types match.)
+     * <p>
+     * Otherwise, if the caller and adapter arity are the same, and the
+     * trailing parameter type of the caller is a reference type identical to
+     * or assignable to the trailing parameter type of the adapter,
+     * the arguments and return values are converted pairwise,
+     * as if by {@link #asType asType} on a fixed arity
+     * method handle.
+     * <p>
+     * Otherwise, the arities differ, or the adapter's trailing parameter
+     * type is not assignable from the corresponding caller type.
+     * In this case, the adapter replaces all trailing arguments from
+     * the original trailing argument position onward, by
+     * a new array of type {@code arrayType}, whose elements
+     * comprise (in order) the replaced arguments.
+     * <p>
+     * The caller type must provides as least enough arguments,
+     * and of the correct type, to satisfy the target's requirement for
+     * positional arguments before the trailing array argument.
+     * Thus, the caller must supply, at a minimum, {@code N-1} arguments,
+     * where {@code N} is the arity of the target.
+     * Also, there must exist conversions from the incoming arguments
+     * to the target's arguments.
+     * As with other uses of plain {@code invoke}, if these basic
+     * requirements are not fulfilled, a {@code WrongMethodTypeException}
+     * may be thrown.
+     * <p>
+     * In all cases, what the target eventually returns is returned unchanged by the adapter.
+     * <p>
+     * In the final case, it is exactly as if the target method handle were
+     * temporarily adapted with a {@linkplain #asCollector fixed arity collector}
+     * to the arity required by the caller type.
+     * (As with {@code asCollector}, if the array length is zero,
+     * a shared constant may be used instead of a new array.
+     * If the implied call to {@code asCollector} would throw
+     * an {@code IllegalArgumentException} or {@code WrongMethodTypeException},
+     * the call to the variable arity adapter must throw
+     * {@code WrongMethodTypeException}.)
+     * <p>
+     * The behavior of {@link #asType asType} is also specialized for
+     * variable arity adapters, to maintain the invariant that
+     * plain, inexact {@code invoke} is always equivalent to an {@code asType}
+     * call to adjust the target type, followed by {@code invokeExact}.
+     * Therefore, a variable arity adapter responds
+     * to an {@code asType} request by building a fixed arity collector,
+     * if and only if the adapter and requested type differ either
+     * in arity or trailing argument type.
+     * The resulting fixed arity collector has its type further adjusted
+     * (if necessary) to the requested type by pairwise conversion,
+     * as if by another application of {@code asType}.
+     * <p>
+     * When a method handle is obtained by executing an {@code ldc} instruction
+     * of a {@code CONSTANT_MethodHandle} constant, and the target method is marked
+     * as a variable arity method (with the modifier bit {@code 0x0080}),
+     * the method handle will accept multiple arities, as if the method handle
+     * constant were created by means of a call to {@code asVarargsCollector}.
+     * <p>
+     * In order to create a collecting adapter which collects a predetermined
+     * number of arguments, and whose type reflects this predetermined number,
+     * use {@link #asCollector asCollector} instead.
+     * <p>
+     * No method handle transformations produce new method handles with
+     * variable arity, unless they are documented as doing so.
+     * Therefore, besides {@code asVarargsCollector},
+     * all methods in {@code MethodHandle} and {@code MethodHandles}
+     * will return a method handle with fixed arity,
+     * except in the cases where they are specified to return their original
+     * operand (e.g., {@code asType} of the method handle's own type).
+     * <p>
+     * Calling {@code asVarargsCollector} on a method handle which is already
+     * of variable arity will produce a method handle with the same type and behavior.
+     * It may (or may not) return the original variable arity method handle.
+     * <p>
+     * Here is an example, of a list-making variable arity method handle:
+     * <blockquote><pre>{@code
+MethodHandle deepToString = publicLookup()
+  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+MethodHandle ts1 = deepToString.asVarargsCollector(Object[].class);
+assertEquals("[won]",   (String) ts1.invokeExact(    new Object[]{"won"}));
+assertEquals("[won]",   (String) ts1.invoke(         new Object[]{"won"}));
+assertEquals("[won]",   (String) ts1.invoke(                      "won" ));
+assertEquals("[[won]]", (String) ts1.invoke((Object) new Object[]{"won"}));
+// findStatic of Arrays.asList(...) produces a variable arity method handle:
+MethodHandle asList = publicLookup()
+  .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class));
+assertEquals(methodType(List.class, Object[].class), asList.type());
+assert(asList.isVarargsCollector());
+assertEquals("[]", asList.invoke().toString());
+assertEquals("[1]", asList.invoke(1).toString());
+assertEquals("[two, too]", asList.invoke("two", "too").toString());
+String[] argv = { "three", "thee", "tee" };
+assertEquals("[three, thee, tee]", asList.invoke(argv).toString());
+assertEquals("[three, thee, tee]", asList.invoke((Object[])argv).toString());
+List ls = (List) asList.invoke((Object)argv);
+assertEquals(1, ls.size());
+assertEquals("[three, thee, tee]", Arrays.toString((Object[])ls.get(0)));
+     * }</pre></blockquote>
+     * <p style="font-size:smaller;">
+     * <em>Discussion:</em>
+     * These rules are designed as a dynamically-typed variation
+     * of the Java rules for variable arity methods.
+     * In both cases, callers to a variable arity method or method handle
+     * can either pass zero or more positional arguments, or else pass
+     * pre-collected arrays of any length.  Users should be aware of the
+     * special role of the final argument, and of the effect of a
+     * type match on that final argument, which determines whether
+     * or not a single trailing argument is interpreted as a whole
+     * array or a single element of an array to be collected.
+     * Note that the dynamic type of the trailing argument has no
+     * effect on this decision, only a comparison between the symbolic
+     * type descriptor of the call site and the type descriptor of the method handle.)
+     *
+     * @param arrayType often {@code Object[]}, the type of the array argument which will collect the arguments
+     * @return a new method handle which can collect any number of trailing arguments
+     *         into an array, before calling the original method handle
+     * @throws NullPointerException if {@code arrayType} is a null reference
+     * @throws IllegalArgumentException if {@code arrayType} is not an array type
+     *         or {@code arrayType} is not assignable to this method handle's trailing parameter type
+     * @see #asCollector
+     * @see #isVarargsCollector
+     * @see #asFixedArity
+     */
+    public MethodHandle asVarargsCollector(Class<?> arrayType) {
+        arrayType.getClass(); // explicit NPE
+        boolean lastMatch = asCollectorChecks(arrayType, 0);
+        if (isVarargsCollector() && lastMatch)
+            return this;
+
+        return new Transformers.VarargsCollector(this);
+    }
+
+    /**
+     * Determines if this method handle
+     * supports {@linkplain #asVarargsCollector variable arity} calls.
+     * Such method handles arise from the following sources:
+     * <ul>
+     * <li>a call to {@linkplain #asVarargsCollector asVarargsCollector}
+     * <li>a call to a {@linkplain java.lang.invoke.MethodHandles.Lookup lookup method}
+     *     which resolves to a variable arity Java method or constructor
+     * <li>an {@code ldc} instruction of a {@code CONSTANT_MethodHandle}
+     *     which resolves to a variable arity Java method or constructor
+     * </ul>
+     * @return true if this method handle accepts more than one arity of plain, inexact {@code invoke} calls
+     * @see #asVarargsCollector
+     * @see #asFixedArity
+     */
+    public boolean isVarargsCollector() {
+        return false;
+    }
+
+    /**
+     * Makes a <em>fixed arity</em> method handle which is otherwise
+     * equivalent to the current method handle.
+     * <p>
+     * If the current method handle is not of
+     * {@linkplain #asVarargsCollector variable arity},
+     * the current method handle is returned.
+     * This is true even if the current method handle
+     * could not be a valid input to {@code asVarargsCollector}.
+     * <p>
+     * Otherwise, the resulting fixed-arity method handle has the same
+     * type and behavior of the current method handle,
+     * except that {@link #isVarargsCollector isVarargsCollector}
+     * will be false.
+     * The fixed-arity method handle may (or may not) be the
+     * a previous argument to {@code asVarargsCollector}.
+     * <p>
+     * Here is an example, of a list-making variable arity method handle:
+     * <blockquote><pre>{@code
+MethodHandle asListVar = publicLookup()
+  .findStatic(Arrays.class, "asList", methodType(List.class, Object[].class))
+  .asVarargsCollector(Object[].class);
+MethodHandle asListFix = asListVar.asFixedArity();
+assertEquals("[1]", asListVar.invoke(1).toString());
+Exception caught = null;
+try { asListFix.invoke((Object)1); }
+catch (Exception ex) { caught = ex; }
+assert(caught instanceof ClassCastException);
+assertEquals("[two, too]", asListVar.invoke("two", "too").toString());
+try { asListFix.invoke("two", "too"); }
+catch (Exception ex) { caught = ex; }
+assert(caught instanceof WrongMethodTypeException);
+Object[] argv = { "three", "thee", "tee" };
+assertEquals("[three, thee, tee]", asListVar.invoke(argv).toString());
+assertEquals("[three, thee, tee]", asListFix.invoke(argv).toString());
+assertEquals(1, ((List) asListVar.invoke((Object)argv)).size());
+assertEquals("[three, thee, tee]", asListFix.invoke((Object)argv).toString());
+     * }</pre></blockquote>
+     *
+     * @return a new method handle which accepts only a fixed number of arguments
+     * @see #asVarargsCollector
+     * @see #isVarargsCollector
+     */
+    public MethodHandle asFixedArity() {
+        // Android-changed: implementation specific.
+        MethodHandle mh = this;
+        if (mh.isVarargsCollector()) {
+            mh = ((Transformers.VarargsCollector) mh).asFixedArity();
+        }
+        assert(!mh.isVarargsCollector());
+        return mh;
+    }
+
+    /**
+     * Binds a value {@code x} to the first argument of a method handle, without invoking it.
+     * The new method handle adapts, as its <i>target</i>,
+     * the current method handle by binding it to the given argument.
+     * The type of the bound handle will be
+     * the same as the type of the target, except that a single leading
+     * reference parameter will be omitted.
+     * <p>
+     * When called, the bound handle inserts the given value {@code x}
+     * as a new leading argument to the target.  The other arguments are
+     * also passed unchanged.
+     * What the target eventually returns is returned unchanged by the bound handle.
+     * <p>
+     * The reference {@code x} must be convertible to the first parameter
+     * type of the target.
+     * <p>
+     * (<em>Note:</em>  Because method handles are immutable, the target method handle
+     * retains its original type and behavior.)
+     * @param x  the value to bind to the first argument of the target
+     * @return a new method handle which prepends the given value to the incoming
+     *         argument list, before calling the original method handle
+     * @throws IllegalArgumentException if the target does not have a
+     *         leading parameter type that is a reference type
+     * @throws ClassCastException if {@code x} cannot be converted
+     *         to the leading parameter type of the target
+     * @see MethodHandles#insertArguments
+     */
+    public MethodHandle bindTo(Object x) {
+        x = type.leadingReferenceParameter().cast(x);  // throw CCE if needed
+
+        return new Transformers.BindTo(this, x);
+    }
+
+    /**
+     * Returns a string representation of the method handle,
+     * starting with the string {@code "MethodHandle"} and
+     * ending with the string representation of the method handle's type.
+     * In other words, this method returns a string equal to the value of:
+     * <blockquote><pre>{@code
+     * "MethodHandle" + type().toString()
+     * }</pre></blockquote>
+     * <p>
+     * (<em>Note:</em>  Future releases of this API may add further information
+     * to the string representation.
+     * Therefore, the present syntax should not be parsed by applications.)
+     *
+     * @return a string representation of the method handle
+     */
+    @Override
+    public String toString() {
+        // Android-changed: Removed debugging support.
+        return "MethodHandle"+type;
+    }
+
+    /** @hide */
+    public int getHandleKind() {
+        return handleKind;
+    }
+
+    /** @hide */
+    protected void transform(EmulatedStackFrame arguments) throws Throwable {
+        throw new AssertionError("MethodHandle.transform should never be called.");
+    }
+
+    /**
+     * Creates a copy of this method handle, copying all relevant data.
+     *
+     * @hide
+     */
+    protected MethodHandle duplicate() {
+        try {
+            return (MethodHandle) this.clone();
+        } catch (CloneNotSupportedException cnse) {
+            throw new AssertionError("Subclass of Transformer is not cloneable");
+        }
+    }
+
+
+    /**
+     * This is the entry point for all transform calls, and dispatches to the protected
+     * transform method. This layer of indirection exists purely for convenience, because
+     * we can invoke-direct on a fixed ArtMethod for all transform variants.
+     *
+     * NOTE: If this extra layer of indirection proves to be a problem, we can get rid
+     * of this layer of indirection at the cost of some additional ugliness.
+     */
+    private void transformInternal(EmulatedStackFrame arguments) throws Throwable {
+        transform(arguments);
+    }
+
+    // Android-changed: Removed implementation details :
+    //
+    // String standardString();
+    // String debugString();
+    //
+    //// Implementation methods.
+    //// Sub-classes can override these default implementations.
+    //// All these methods assume arguments are already validated.
+    //
+    // Other transforms to do:  convert, explicitCast, permute, drop, filter, fold, GWT, catch
+    //
+    // BoundMethodHandle bindArgumentL(int pos, Object value);
+    // /*non-public*/ MethodHandle setVarargs(MemberName member);
+    // /*non-public*/ MethodHandle viewAsType(MethodType newType, boolean strict);
+    // /*non-public*/ boolean viewAsTypeChecks(MethodType newType, boolean strict);
+    //
+    // Decoding
+    //
+    // /*non-public*/ LambdaForm internalForm();
+    // /*non-public*/ MemberName internalMemberName();
+    // /*non-public*/ Class<?> internalCallerClass();
+    // /*non-public*/ MethodHandleImpl.Intrinsic intrinsicName();
+    // /*non-public*/ MethodHandle withInternalMemberName(MemberName member, boolean isInvokeSpecial);
+    // /*non-public*/ boolean isInvokeSpecial();
+    // /*non-public*/ Object internalValues();
+    // /*non-public*/ Object internalProperties();
+    //
+    //// Method handle implementation methods.
+    //// Sub-classes can override these default implementations.
+    //// All these methods assume arguments are already validated.
+    //
+    // /*non-public*/ abstract MethodHandle copyWith(MethodType mt, LambdaForm lf);
+    // abstract BoundMethodHandle rebind();
+    // /*non-public*/ void updateForm(LambdaForm newForm);
+    // /*non-public*/ void customize();
+    // private static final long FORM_OFFSET;
 }
diff --git a/java/lang/invoke/MethodHandles.java b/java/lang/invoke/MethodHandles.java
index f27ad98..a1b861d 100644
--- a/java/lang/invoke/MethodHandles.java
+++ b/java/lang/invoke/MethodHandles.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,129 +25,3461 @@
 
 package java.lang.invoke;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
+import java.lang.reflect.*;
+import java.nio.ByteOrder;
 import java.util.List;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
 
+import dalvik.system.VMStack;
+import sun.invoke.util.VerifyAccess;
+import sun.invoke.util.Wrapper;
+import static java.lang.invoke.MethodHandleStatics.*;
+
+/**
+ * This class consists exclusively of static methods that operate on or return
+ * method handles. They fall into several categories:
+ * <ul>
+ * <li>Lookup methods which help create method handles for methods and fields.
+ * <li>Combinator methods, which combine or transform pre-existing method handles into new ones.
+ * <li>Other factory methods to create method handles that emulate other common JVM operations or control flow patterns.
+ * </ul>
+ * <p>
+ * @author John Rose, JSR 292 EG
+ * @since 1.7
+ */
 public class MethodHandles {
 
-    public static Lookup lookup() { return null; }
+    private MethodHandles() { }  // do not instantiate
 
-    public static Lookup publicLookup() { return null; }
+    // BEGIN Android-added: unsupported() helper function.
+    // TODO(b/65872996): Remove when complete.
+    private static void unsupported(String msg) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException(msg);
+    }
+    // END Android-added: unsupported() helper function.
 
-    public static <T extends Member> T
-    reflectAs(Class<T> expected, MethodHandle target) { return null; }
+    // Android-changed: We do not use MemberName / MethodHandleImpl.
+    //
+    // private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory();
+    // static { MethodHandleImpl.initStatics(); }
+    // See IMPL_LOOKUP below.
 
-    public static final
-    class Lookup {
-        public static final int PUBLIC = 0;
+    //// Method handle creation from ordinary methods.
 
-        public static final int PRIVATE = 0;
-
-        public static final int PROTECTED = 0;
-
-        public static final int PACKAGE =  0;
-
-        public Class<?> lookupClass() { return null; }
-
-        public int lookupModes() { return 0; }
-
-        public Lookup in(Class<?> requestedLookupClass) { return null; }
-
-        public
-        MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle findSpecial(Class<?> refc, String name, MethodType type,
-                                        Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
-
-        public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
-
-        public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
-
-        public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException { return null; }
-
-        public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { return null; }
-
-        public MethodHandle unreflect(Method m) throws IllegalAccessException { return null; }
-
-        public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException { return null; }
-
-        public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException { return null; }
-
-        public MethodHandle unreflectGetter(Field f) throws IllegalAccessException { return null; }
-
-        public MethodHandle unreflectSetter(Field f) throws IllegalAccessException { return null; }
-
-        public MethodHandleInfo revealDirect(MethodHandle target) { return null; }
-
+    /**
+     * Returns a {@link Lookup lookup object} with
+     * full capabilities to emulate all supported bytecode behaviors of the caller.
+     * These capabilities include <a href="MethodHandles.Lookup.html#privacc">private access</a> to the caller.
+     * Factory methods on the lookup object can create
+     * <a href="MethodHandleInfo.html#directmh">direct method handles</a>
+     * for any member that the caller has access to via bytecodes,
+     * including protected and private fields and methods.
+     * This lookup object is a <em>capability</em> which may be delegated to trusted agents.
+     * Do not store it in place where untrusted code can access it.
+     * <p>
+     * This method is caller sensitive, which means that it may return different
+     * values to different callers.
+     * <p>
+     * For any given caller class {@code C}, the lookup object returned by this call
+     * has equivalent capabilities to any lookup object
+     * supplied by the JVM to the bootstrap method of an
+     * <a href="package-summary.html#indyinsn">invokedynamic instruction</a>
+     * executing in the same caller class {@code C}.
+     * @return a lookup object for the caller of this method, with private access
+     */
+    // Android-changed: Remove caller sensitive.
+    // @CallerSensitive
+    public static Lookup lookup() {
+        // Android-changed: Do not use Reflection.getCallerClass().
+        return new Lookup(VMStack.getStackClass1());
     }
 
-    public static
-    MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
+    /**
+     * Returns a {@link Lookup lookup object} which is trusted minimally.
+     * It can only be used to create method handles to
+     * publicly accessible fields and methods.
+     * <p>
+     * As a matter of pure convention, the {@linkplain Lookup#lookupClass lookup class}
+     * of this lookup object will be {@link java.lang.Object}.
+     *
+     * <p style="font-size:smaller;">
+     * <em>Discussion:</em>
+     * The lookup class can be changed to any other class {@code C} using an expression of the form
+     * {@link Lookup#in publicLookup().in(C.class)}.
+     * Since all classes have equal access to public names,
+     * such a change would confer no new access rights.
+     * A public lookup object is always subject to
+     * <a href="MethodHandles.Lookup.html#secmgr">security manager checks</a>.
+     * Also, it cannot access
+     * <a href="MethodHandles.Lookup.html#callsens">caller sensitive methods</a>.
+     * @return a lookup object which is trusted minimally
+     */
+    public static Lookup publicLookup() {
+        return Lookup.PUBLIC_LOOKUP;
+    }
 
-    public static
-    MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException { return null; }
+    /**
+     * Performs an unchecked "crack" of a
+     * <a href="MethodHandleInfo.html#directmh">direct method handle</a>.
+     * The result is as if the user had obtained a lookup object capable enough
+     * to crack the target method handle, called
+     * {@link java.lang.invoke.MethodHandles.Lookup#revealDirect Lookup.revealDirect}
+     * on the target to obtain its symbolic reference, and then called
+     * {@link java.lang.invoke.MethodHandleInfo#reflectAs MethodHandleInfo.reflectAs}
+     * to resolve the symbolic reference to a member.
+     * <p>
+     * If there is a security manager, its {@code checkPermission} method
+     * is called with a {@code ReflectPermission("suppressAccessChecks")} permission.
+     * @param <T> the desired type of the result, either {@link Member} or a subtype
+     * @param target a direct method handle to crack into symbolic reference components
+     * @param expected a class object representing the desired result type {@code T}
+     * @return a reference to the method, constructor, or field object
+     * @exception SecurityException if the caller is not privileged to call {@code setAccessible}
+     * @exception NullPointerException if either argument is {@code null}
+     * @exception IllegalArgumentException if the target is not a direct method handle
+     * @exception ClassCastException if the member is not of the expected type
+     * @since 1.8
+     */
+    public static <T extends Member> T
+    reflectAs(Class<T> expected, MethodHandle target) {
+        MethodHandleImpl directTarget = getMethodHandleImpl(target);
+        // Given that this is specified to be an "unchecked" crack, we can directly allocate
+        // a member from the underlying ArtField / Method and bypass all associated access checks.
+        return expected.cast(directTarget.getMemberInternal());
+    }
 
+    /**
+     * A <em>lookup object</em> is a factory for creating method handles,
+     * when the creation requires access checking.
+     * Method handles do not perform
+     * access checks when they are called, but rather when they are created.
+     * Therefore, method handle access
+     * restrictions must be enforced when a method handle is created.
+     * The caller class against which those restrictions are enforced
+     * is known as the {@linkplain #lookupClass lookup class}.
+     * <p>
+     * A lookup class which needs to create method handles will call
+     * {@link #lookup MethodHandles.lookup} to create a factory for itself.
+     * When the {@code Lookup} factory object is created, the identity of the lookup class is
+     * determined, and securely stored in the {@code Lookup} object.
+     * The lookup class (or its delegates) may then use factory methods
+     * on the {@code Lookup} object to create method handles for access-checked members.
+     * This includes all methods, constructors, and fields which are allowed to the lookup class,
+     * even private ones.
+     *
+     * <h1><a name="lookups"></a>Lookup Factory Methods</h1>
+     * The factory methods on a {@code Lookup} object correspond to all major
+     * use cases for methods, constructors, and fields.
+     * Each method handle created by a factory method is the functional
+     * equivalent of a particular <em>bytecode behavior</em>.
+     * (Bytecode behaviors are described in section 5.4.3.5 of the Java Virtual Machine Specification.)
+     * Here is a summary of the correspondence between these factory methods and
+     * the behavior the resulting method handles:
+     * <table border=1 cellpadding=5 summary="lookup method behaviors">
+     * <tr>
+     *     <th><a name="equiv"></a>lookup expression</th>
+     *     <th>member</th>
+     *     <th>bytecode behavior</th>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findGetter lookup.findGetter(C.class,"f",FT.class)}</td>
+     *     <td>{@code FT f;}</td><td>{@code (T) this.f;}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticGetter lookup.findStaticGetter(C.class,"f",FT.class)}</td>
+     *     <td>{@code static}<br>{@code FT f;}</td><td>{@code (T) C.f;}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findSetter lookup.findSetter(C.class,"f",FT.class)}</td>
+     *     <td>{@code FT f;}</td><td>{@code this.f = x;}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findStaticSetter lookup.findStaticSetter(C.class,"f",FT.class)}</td>
+     *     <td>{@code static}<br>{@code FT f;}</td><td>{@code C.f = arg;}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findVirtual lookup.findVirtual(C.class,"m",MT)}</td>
+     *     <td>{@code T m(A*);}</td><td>{@code (T) this.m(arg*);}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findStatic lookup.findStatic(C.class,"m",MT)}</td>
+     *     <td>{@code static}<br>{@code T m(A*);}</td><td>{@code (T) C.m(arg*);}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findSpecial lookup.findSpecial(C.class,"m",MT,this.class)}</td>
+     *     <td>{@code T m(A*);}</td><td>{@code (T) super.m(arg*);}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#findConstructor lookup.findConstructor(C.class,MT)}</td>
+     *     <td>{@code C(A*);}</td><td>{@code new C(arg*);}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectGetter lookup.unreflectGetter(aField)}</td>
+     *     <td>({@code static})?<br>{@code FT f;}</td><td>{@code (FT) aField.get(thisOrNull);}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectSetter lookup.unreflectSetter(aField)}</td>
+     *     <td>({@code static})?<br>{@code FT f;}</td><td>{@code aField.set(thisOrNull, arg);}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td>
+     *     <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflectConstructor lookup.unreflectConstructor(aConstructor)}</td>
+     *     <td>{@code C(A*);}</td><td>{@code (C) aConstructor.newInstance(arg*);}</td>
+     * </tr>
+     * <tr>
+     *     <td>{@link java.lang.invoke.MethodHandles.Lookup#unreflect lookup.unreflect(aMethod)}</td>
+     *     <td>({@code static})?<br>{@code T m(A*);}</td><td>{@code (T) aMethod.invoke(thisOrNull, arg*);}</td>
+     * </tr>
+     * </table>
+     *
+     * Here, the type {@code C} is the class or interface being searched for a member,
+     * documented as a parameter named {@code refc} in the lookup methods.
+     * The method type {@code MT} is composed from the return type {@code T}
+     * and the sequence of argument types {@code A*}.
+     * The constructor also has a sequence of argument types {@code A*} and
+     * is deemed to return the newly-created object of type {@code C}.
+     * Both {@code MT} and the field type {@code FT} are documented as a parameter named {@code type}.
+     * The formal parameter {@code this} stands for the self-reference of type {@code C};
+     * if it is present, it is always the leading argument to the method handle invocation.
+     * (In the case of some {@code protected} members, {@code this} may be
+     * restricted in type to the lookup class; see below.)
+     * The name {@code arg} stands for all the other method handle arguments.
+     * In the code examples for the Core Reflection API, the name {@code thisOrNull}
+     * stands for a null reference if the accessed method or field is static,
+     * and {@code this} otherwise.
+     * The names {@code aMethod}, {@code aField}, and {@code aConstructor} stand
+     * for reflective objects corresponding to the given members.
+     * <p>
+     * In cases where the given member is of variable arity (i.e., a method or constructor)
+     * the returned method handle will also be of {@linkplain MethodHandle#asVarargsCollector variable arity}.
+     * In all other cases, the returned method handle will be of fixed arity.
+     * <p style="font-size:smaller;">
+     * <em>Discussion:</em>
+     * The equivalence between looked-up method handles and underlying
+     * class members and bytecode behaviors
+     * can break down in a few ways:
+     * <ul style="font-size:smaller;">
+     * <li>If {@code C} is not symbolically accessible from the lookup class's loader,
+     * the lookup can still succeed, even when there is no equivalent
+     * Java expression or bytecoded constant.
+     * <li>Likewise, if {@code T} or {@code MT}
+     * is not symbolically accessible from the lookup class's loader,
+     * the lookup can still succeed.
+     * For example, lookups for {@code MethodHandle.invokeExact} and
+     * {@code MethodHandle.invoke} will always succeed, regardless of requested type.
+     * <li>If there is a security manager installed, it can forbid the lookup
+     * on various grounds (<a href="MethodHandles.Lookup.html#secmgr">see below</a>).
+     * By contrast, the {@code ldc} instruction on a {@code CONSTANT_MethodHandle}
+     * constant is not subject to security manager checks.
+     * <li>If the looked-up method has a
+     * <a href="MethodHandle.html#maxarity">very large arity</a>,
+     * the method handle creation may fail, due to the method handle
+     * type having too many parameters.
+     * </ul>
+     *
+     * <h1><a name="access"></a>Access checking</h1>
+     * Access checks are applied in the factory methods of {@code Lookup},
+     * when a method handle is created.
+     * This is a key difference from the Core Reflection API, since
+     * {@link java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
+     * performs access checking against every caller, on every call.
+     * <p>
+     * All access checks start from a {@code Lookup} object, which
+     * compares its recorded lookup class against all requests to
+     * create method handles.
+     * A single {@code Lookup} object can be used to create any number
+     * of access-checked method handles, all checked against a single
+     * lookup class.
+     * <p>
+     * A {@code Lookup} object can be shared with other trusted code,
+     * such as a metaobject protocol.
+     * A shared {@code Lookup} object delegates the capability
+     * to create method handles on private members of the lookup class.
+     * Even if privileged code uses the {@code Lookup} object,
+     * the access checking is confined to the privileges of the
+     * original lookup class.
+     * <p>
+     * A lookup can fail, because
+     * the containing class is not accessible to the lookup class, or
+     * because the desired class member is missing, or because the
+     * desired class member is not accessible to the lookup class, or
+     * because the lookup object is not trusted enough to access the member.
+     * In any of these cases, a {@code ReflectiveOperationException} will be
+     * thrown from the attempted lookup.  The exact class will be one of
+     * the following:
+     * <ul>
+     * <li>NoSuchMethodException &mdash; if a method is requested but does not exist
+     * <li>NoSuchFieldException &mdash; if a field is requested but does not exist
+     * <li>IllegalAccessException &mdash; if the member exists but an access check fails
+     * </ul>
+     * <p>
+     * In general, the conditions under which a method handle may be
+     * looked up for a method {@code M} are no more restrictive than the conditions
+     * under which the lookup class could have compiled, verified, and resolved a call to {@code M}.
+     * Where the JVM would raise exceptions like {@code NoSuchMethodError},
+     * a method handle lookup will generally raise a corresponding
+     * checked exception, such as {@code NoSuchMethodException}.
+     * And the effect of invoking the method handle resulting from the lookup
+     * is <a href="MethodHandles.Lookup.html#equiv">exactly equivalent</a>
+     * to executing the compiled, verified, and resolved call to {@code M}.
+     * The same point is true of fields and constructors.
+     * <p style="font-size:smaller;">
+     * <em>Discussion:</em>
+     * Access checks only apply to named and reflected methods,
+     * constructors, and fields.
+     * Other method handle creation methods, such as
+     * {@link MethodHandle#asType MethodHandle.asType},
+     * do not require any access checks, and are used
+     * independently of any {@code Lookup} object.
+     * <p>
+     * If the desired member is {@code protected}, the usual JVM rules apply,
+     * including the requirement that the lookup class must be either be in the
+     * same package as the desired member, or must inherit that member.
+     * (See the Java Virtual Machine Specification, sections 4.9.2, 5.4.3.5, and 6.4.)
+     * In addition, if the desired member is a non-static field or method
+     * in a different package, the resulting method handle may only be applied
+     * to objects of the lookup class or one of its subclasses.
+     * This requirement is enforced by narrowing the type of the leading
+     * {@code this} parameter from {@code C}
+     * (which will necessarily be a superclass of the lookup class)
+     * to the lookup class itself.
+     * <p>
+     * The JVM imposes a similar requirement on {@code invokespecial} instruction,
+     * that the receiver argument must match both the resolved method <em>and</em>
+     * the current class.  Again, this requirement is enforced by narrowing the
+     * type of the leading parameter to the resulting method handle.
+     * (See the Java Virtual Machine Specification, section 4.10.1.9.)
+     * <p>
+     * The JVM represents constructors and static initializer blocks as internal methods
+     * with special names ({@code "<init>"} and {@code "<clinit>"}).
+     * The internal syntax of invocation instructions allows them to refer to such internal
+     * methods as if they were normal methods, but the JVM bytecode verifier rejects them.
+     * A lookup of such an internal method will produce a {@code NoSuchMethodException}.
+     * <p>
+     * In some cases, access between nested classes is obtained by the Java compiler by creating
+     * an wrapper method to access a private method of another class
+     * in the same top-level declaration.
+     * For example, a nested class {@code C.D}
+     * can access private members within other related classes such as
+     * {@code C}, {@code C.D.E}, or {@code C.B},
+     * but the Java compiler may need to generate wrapper methods in
+     * those related classes.  In such cases, a {@code Lookup} object on
+     * {@code C.E} would be unable to those private members.
+     * A workaround for this limitation is the {@link Lookup#in Lookup.in} method,
+     * which can transform a lookup on {@code C.E} into one on any of those other
+     * classes, without special elevation of privilege.
+     * <p>
+     * The accesses permitted to a given lookup object may be limited,
+     * according to its set of {@link #lookupModes lookupModes},
+     * to a subset of members normally accessible to the lookup class.
+     * For example, the {@link #publicLookup publicLookup}
+     * method produces a lookup object which is only allowed to access
+     * public members in public classes.
+     * The caller sensitive method {@link #lookup lookup}
+     * produces a lookup object with full capabilities relative to
+     * its caller class, to emulate all supported bytecode behaviors.
+     * Also, the {@link Lookup#in Lookup.in} method may produce a lookup object
+     * with fewer access modes than the original lookup object.
+     *
+     * <p style="font-size:smaller;">
+     * <a name="privacc"></a>
+     * <em>Discussion of private access:</em>
+     * We say that a lookup has <em>private access</em>
+     * if its {@linkplain #lookupModes lookup modes}
+     * include the possibility of accessing {@code private} members.
+     * As documented in the relevant methods elsewhere,
+     * only lookups with private access possess the following capabilities:
+     * <ul style="font-size:smaller;">
+     * <li>access private fields, methods, and constructors of the lookup class
+     * <li>create method handles which invoke <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a> methods,
+     *     such as {@code Class.forName}
+     * <li>create method handles which {@link Lookup#findSpecial emulate invokespecial} instructions
+     * <li>avoid <a href="MethodHandles.Lookup.html#secmgr">package access checks</a>
+     *     for classes accessible to the lookup class
+     * <li>create {@link Lookup#in delegated lookup objects} which have private access to other classes
+     *     within the same package member
+     * </ul>
+     * <p style="font-size:smaller;">
+     * Each of these permissions is a consequence of the fact that a lookup object
+     * with private access can be securely traced back to an originating class,
+     * whose <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> and Java language access permissions
+     * can be reliably determined and emulated by method handles.
+     *
+     * <h1><a name="secmgr"></a>Security manager interactions</h1>
+     * Although bytecode instructions can only refer to classes in
+     * a related class loader, this API can search for methods in any
+     * class, as long as a reference to its {@code Class} object is
+     * available.  Such cross-loader references are also possible with the
+     * Core Reflection API, and are impossible to bytecode instructions
+     * such as {@code invokestatic} or {@code getfield}.
+     * There is a {@linkplain java.lang.SecurityManager security manager API}
+     * to allow applications to check such cross-loader references.
+     * These checks apply to both the {@code MethodHandles.Lookup} API
+     * and the Core Reflection API
+     * (as found on {@link java.lang.Class Class}).
+     * <p>
+     * If a security manager is present, member lookups are subject to
+     * additional checks.
+     * From one to three calls are made to the security manager.
+     * Any of these calls can refuse access by throwing a
+     * {@link java.lang.SecurityException SecurityException}.
+     * Define {@code smgr} as the security manager,
+     * {@code lookc} as the lookup class of the current lookup object,
+     * {@code refc} as the containing class in which the member
+     * is being sought, and {@code defc} as the class in which the
+     * member is actually defined.
+     * The value {@code lookc} is defined as <em>not present</em>
+     * if the current lookup object does not have
+     * <a href="MethodHandles.Lookup.html#privacc">private access</a>.
+     * The calls are made according to the following rules:
+     * <ul>
+     * <li><b>Step 1:</b>
+     *     If {@code lookc} is not present, or if its class loader is not
+     *     the same as or an ancestor of the class loader of {@code refc},
+     *     then {@link SecurityManager#checkPackageAccess
+     *     smgr.checkPackageAccess(refcPkg)} is called,
+     *     where {@code refcPkg} is the package of {@code refc}.
+     * <li><b>Step 2:</b>
+     *     If the retrieved member is not public and
+     *     {@code lookc} is not present, then
+     *     {@link SecurityManager#checkPermission smgr.checkPermission}
+     *     with {@code RuntimePermission("accessDeclaredMembers")} is called.
+     * <li><b>Step 3:</b>
+     *     If the retrieved member is not public,
+     *     and if {@code lookc} is not present,
+     *     and if {@code defc} and {@code refc} are different,
+     *     then {@link SecurityManager#checkPackageAccess
+     *     smgr.checkPackageAccess(defcPkg)} is called,
+     *     where {@code defcPkg} is the package of {@code defc}.
+     * </ul>
+     * Security checks are performed after other access checks have passed.
+     * Therefore, the above rules presuppose a member that is public,
+     * or else that is being accessed from a lookup class that has
+     * rights to access the member.
+     *
+     * <h1><a name="callsens"></a>Caller sensitive methods</h1>
+     * A small number of Java methods have a special property called caller sensitivity.
+     * A <em>caller-sensitive</em> method can behave differently depending on the
+     * identity of its immediate caller.
+     * <p>
+     * If a method handle for a caller-sensitive method is requested,
+     * the general rules for <a href="MethodHandles.Lookup.html#equiv">bytecode behaviors</a> apply,
+     * but they take account of the lookup class in a special way.
+     * The resulting method handle behaves as if it were called
+     * from an instruction contained in the lookup class,
+     * so that the caller-sensitive method detects the lookup class.
+     * (By contrast, the invoker of the method handle is disregarded.)
+     * Thus, in the case of caller-sensitive methods,
+     * different lookup classes may give rise to
+     * differently behaving method handles.
+     * <p>
+     * In cases where the lookup object is
+     * {@link #publicLookup publicLookup()},
+     * or some other lookup object without
+     * <a href="MethodHandles.Lookup.html#privacc">private access</a>,
+     * the lookup class is disregarded.
+     * In such cases, no caller-sensitive method handle can be created,
+     * access is forbidden, and the lookup fails with an
+     * {@code IllegalAccessException}.
+     * <p style="font-size:smaller;">
+     * <em>Discussion:</em>
+     * For example, the caller-sensitive method
+     * {@link java.lang.Class#forName(String) Class.forName(x)}
+     * can return varying classes or throw varying exceptions,
+     * depending on the class loader of the class that calls it.
+     * A public lookup of {@code Class.forName} will fail, because
+     * there is no reasonable way to determine its bytecode behavior.
+     * <p style="font-size:smaller;">
+     * If an application caches method handles for broad sharing,
+     * it should use {@code publicLookup()} to create them.
+     * If there is a lookup of {@code Class.forName}, it will fail,
+     * and the application must take appropriate action in that case.
+     * It may be that a later lookup, perhaps during the invocation of a
+     * bootstrap method, can incorporate the specific identity
+     * of the caller, making the method accessible.
+     * <p style="font-size:smaller;">
+     * The function {@code MethodHandles.lookup} is caller sensitive
+     * so that there can be a secure foundation for lookups.
+     * Nearly all other methods in the JSR 292 API rely on lookup
+     * objects to check access requests.
+     */
+    // Android-changed: Change link targets from MethodHandles#[public]Lookup to
+    // #[public]Lookup to work around complaints from javadoc.
+    public static final
+    class Lookup {
+        /** The class on behalf of whom the lookup is being performed. */
+        /* @NonNull */ private final Class<?> lookupClass;
+
+        /** The allowed sorts of members which may be looked up (PUBLIC, etc.). */
+        private final int allowedModes;
+
+        /** A single-bit mask representing {@code public} access,
+         *  which may contribute to the result of {@link #lookupModes lookupModes}.
+         *  The value, {@code 0x01}, happens to be the same as the value of the
+         *  {@code public} {@linkplain java.lang.reflect.Modifier#PUBLIC modifier bit}.
+         */
+        public static final int PUBLIC = Modifier.PUBLIC;
+
+        /** A single-bit mask representing {@code private} access,
+         *  which may contribute to the result of {@link #lookupModes lookupModes}.
+         *  The value, {@code 0x02}, happens to be the same as the value of the
+         *  {@code private} {@linkplain java.lang.reflect.Modifier#PRIVATE modifier bit}.
+         */
+        public static final int PRIVATE = Modifier.PRIVATE;
+
+        /** A single-bit mask representing {@code protected} access,
+         *  which may contribute to the result of {@link #lookupModes lookupModes}.
+         *  The value, {@code 0x04}, happens to be the same as the value of the
+         *  {@code protected} {@linkplain java.lang.reflect.Modifier#PROTECTED modifier bit}.
+         */
+        public static final int PROTECTED = Modifier.PROTECTED;
+
+        /** A single-bit mask representing {@code package} access (default access),
+         *  which may contribute to the result of {@link #lookupModes lookupModes}.
+         *  The value is {@code 0x08}, which does not correspond meaningfully to
+         *  any particular {@linkplain java.lang.reflect.Modifier modifier bit}.
+         */
+        public static final int PACKAGE = Modifier.STATIC;
+
+        private static final int ALL_MODES = (PUBLIC | PRIVATE | PROTECTED | PACKAGE);
+
+        // Android-note: Android has no notion of a trusted lookup. If required, such lookups
+        // are performed by the runtime. As a result, we always use lookupClass, which will always
+        // be non-null in our implementation.
+        //
+        // private static final int TRUSTED   = -1;
+
+        private static int fixmods(int mods) {
+            mods &= (ALL_MODES - PACKAGE);
+            return (mods != 0) ? mods : PACKAGE;
+        }
+
+        /** Tells which class is performing the lookup.  It is this class against
+         *  which checks are performed for visibility and access permissions.
+         *  <p>
+         *  The class implies a maximum level of access permission,
+         *  but the permissions may be additionally limited by the bitmask
+         *  {@link #lookupModes lookupModes}, which controls whether non-public members
+         *  can be accessed.
+         *  @return the lookup class, on behalf of which this lookup object finds members
+         */
+        public Class<?> lookupClass() {
+            return lookupClass;
+        }
+
+        /** Tells which access-protection classes of members this lookup object can produce.
+         *  The result is a bit-mask of the bits
+         *  {@linkplain #PUBLIC PUBLIC (0x01)},
+         *  {@linkplain #PRIVATE PRIVATE (0x02)},
+         *  {@linkplain #PROTECTED PROTECTED (0x04)},
+         *  and {@linkplain #PACKAGE PACKAGE (0x08)}.
+         *  <p>
+         *  A freshly-created lookup object
+         *  on the {@linkplain java.lang.invoke.MethodHandles#lookup() caller's class}
+         *  has all possible bits set, since the caller class can access all its own members.
+         *  A lookup object on a new lookup class
+         *  {@linkplain java.lang.invoke.MethodHandles.Lookup#in created from a previous lookup object}
+         *  may have some mode bits set to zero.
+         *  The purpose of this is to restrict access via the new lookup object,
+         *  so that it can access only names which can be reached by the original
+         *  lookup object, and also by the new lookup class.
+         *  @return the lookup modes, which limit the kinds of access performed by this lookup object
+         */
+        public int lookupModes() {
+            return allowedModes & ALL_MODES;
+        }
+
+        /** Embody the current class (the lookupClass) as a lookup class
+         * for method handle creation.
+         * Must be called by from a method in this package,
+         * which in turn is called by a method not in this package.
+         */
+        Lookup(Class<?> lookupClass) {
+            this(lookupClass, ALL_MODES);
+            // make sure we haven't accidentally picked up a privileged class:
+            checkUnprivilegedlookupClass(lookupClass, ALL_MODES);
+        }
+
+        private Lookup(Class<?> lookupClass, int allowedModes) {
+            this.lookupClass = lookupClass;
+            this.allowedModes = allowedModes;
+        }
+
+        /**
+         * Creates a lookup on the specified new lookup class.
+         * The resulting object will report the specified
+         * class as its own {@link #lookupClass lookupClass}.
+         * <p>
+         * However, the resulting {@code Lookup} object is guaranteed
+         * to have no more access capabilities than the original.
+         * In particular, access capabilities can be lost as follows:<ul>
+         * <li>If the new lookup class differs from the old one,
+         * protected members will not be accessible by virtue of inheritance.
+         * (Protected members may continue to be accessible because of package sharing.)
+         * <li>If the new lookup class is in a different package
+         * than the old one, protected and default (package) members will not be accessible.
+         * <li>If the new lookup class is not within the same package member
+         * as the old one, private members will not be accessible.
+         * <li>If the new lookup class is not accessible to the old lookup class,
+         * then no members, not even public members, will be accessible.
+         * (In all other cases, public members will continue to be accessible.)
+         * </ul>
+         *
+         * @param requestedLookupClass the desired lookup class for the new lookup object
+         * @return a lookup object which reports the desired lookup class
+         * @throws NullPointerException if the argument is null
+         */
+        public Lookup in(Class<?> requestedLookupClass) {
+            requestedLookupClass.getClass();  // null check
+            // Android-changed: There's no notion of a trusted lookup.
+            // if (allowedModes == TRUSTED)  // IMPL_LOOKUP can make any lookup at all
+            //    return new Lookup(requestedLookupClass, ALL_MODES);
+
+            if (requestedLookupClass == this.lookupClass)
+                return this;  // keep same capabilities
+            int newModes = (allowedModes & (ALL_MODES & ~PROTECTED));
+            if ((newModes & PACKAGE) != 0
+                && !VerifyAccess.isSamePackage(this.lookupClass, requestedLookupClass)) {
+                newModes &= ~(PACKAGE|PRIVATE);
+            }
+            // Allow nestmate lookups to be created without special privilege:
+            if ((newModes & PRIVATE) != 0
+                && !VerifyAccess.isSamePackageMember(this.lookupClass, requestedLookupClass)) {
+                newModes &= ~PRIVATE;
+            }
+            if ((newModes & PUBLIC) != 0
+                && !VerifyAccess.isClassAccessible(requestedLookupClass, this.lookupClass, allowedModes)) {
+                // The requested class it not accessible from the lookup class.
+                // No permissions.
+                newModes = 0;
+            }
+            checkUnprivilegedlookupClass(requestedLookupClass, newModes);
+            return new Lookup(requestedLookupClass, newModes);
+        }
+
+        // Make sure outer class is initialized first.
+        //
+        // Android-changed: Removed unnecessary reference to IMPL_NAMES.
+        // static { IMPL_NAMES.getClass(); }
+
+        /** Version of lookup which is trusted minimally.
+         *  It can only be used to create method handles to
+         *  publicly accessible members.
+         */
+        static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, PUBLIC);
+
+        /** Package-private version of lookup which is trusted. */
+        static final Lookup IMPL_LOOKUP = new Lookup(Object.class, ALL_MODES);
+
+        private static void checkUnprivilegedlookupClass(Class<?> lookupClass, int allowedModes) {
+            String name = lookupClass.getName();
+            if (name.startsWith("java.lang.invoke."))
+                throw newIllegalArgumentException("illegal lookupClass: "+lookupClass);
+
+            // For caller-sensitive MethodHandles.lookup()
+            // disallow lookup more restricted packages
+            //
+            // Android-changed: The bootstrap classloader isn't null.
+            if (allowedModes == ALL_MODES &&
+                    lookupClass.getClassLoader() == Object.class.getClassLoader()) {
+                if (name.startsWith("java.") ||
+                        (name.startsWith("sun.")
+                                && !name.startsWith("sun.invoke.")
+                                && !name.equals("sun.reflect.ReflectionFactory"))) {
+                    throw newIllegalArgumentException("illegal lookupClass: " + lookupClass);
+                }
+            }
+        }
+
+        /**
+         * Displays the name of the class from which lookups are to be made.
+         * (The name is the one reported by {@link java.lang.Class#getName() Class.getName}.)
+         * If there are restrictions on the access permitted to this lookup,
+         * this is indicated by adding a suffix to the class name, consisting
+         * of a slash and a keyword.  The keyword represents the strongest
+         * allowed access, and is chosen as follows:
+         * <ul>
+         * <li>If no access is allowed, the suffix is "/noaccess".
+         * <li>If only public access is allowed, the suffix is "/public".
+         * <li>If only public and package access are allowed, the suffix is "/package".
+         * <li>If only public, package, and private access are allowed, the suffix is "/private".
+         * </ul>
+         * If none of the above cases apply, it is the case that full
+         * access (public, package, private, and protected) is allowed.
+         * In this case, no suffix is added.
+         * This is true only of an object obtained originally from
+         * {@link java.lang.invoke.MethodHandles#lookup MethodHandles.lookup}.
+         * Objects created by {@link java.lang.invoke.MethodHandles.Lookup#in Lookup.in}
+         * always have restricted access, and will display a suffix.
+         * <p>
+         * (It may seem strange that protected access should be
+         * stronger than private access.  Viewed independently from
+         * package access, protected access is the first to be lost,
+         * because it requires a direct subclass relationship between
+         * caller and callee.)
+         * @see #in
+         */
+        @Override
+        public String toString() {
+            String cname = lookupClass.getName();
+            switch (allowedModes) {
+            case 0:  // no privileges
+                return cname + "/noaccess";
+            case PUBLIC:
+                return cname + "/public";
+            case PUBLIC|PACKAGE:
+                return cname + "/package";
+            case ALL_MODES & ~PROTECTED:
+                return cname + "/private";
+            case ALL_MODES:
+                return cname;
+            // Android-changed: No support for TRUSTED callers.
+            // case TRUSTED:
+            //    return "/trusted";  // internal only; not exported
+            default:  // Should not happen, but it's a bitfield...
+                cname = cname + "/" + Integer.toHexString(allowedModes);
+                assert(false) : cname;
+                return cname;
+            }
+        }
+
+        /**
+         * Produces a method handle for a static method.
+         * The type of the method handle will be that of the method.
+         * (Since static methods do not take receivers, there is no
+         * additional receiver argument inserted into the method handle type,
+         * as there would be with {@link #findVirtual findVirtual} or {@link #findSpecial findSpecial}.)
+         * The method and all its argument types must be accessible to the lookup object.
+         * <p>
+         * The returned method handle will have
+         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+         * the method's variable arity modifier bit ({@code 0x0080}) is set.
+         * <p>
+         * If the returned method handle is invoked, the method's class will
+         * be initialized, if it has not already been initialized.
+         * <p><b>Example:</b>
+         * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_asList = publicLookup().findStatic(Arrays.class,
+  "asList", methodType(List.class, Object[].class));
+assertEquals("[x, y]", MH_asList.invoke("x", "y").toString());
+         * }</pre></blockquote>
+         * @param refc the class from which the method is accessed
+         * @param name the name of the method
+         * @param type the type of the method
+         * @return the desired method handle
+         * @throws NoSuchMethodException if the method does not exist
+         * @throws IllegalAccessException if access checking fails,
+         *                                or if the method is not {@code static},
+         *                                or if the method's variable arity modifier bit
+         *                                is set and {@code asVarargsCollector} fails
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         */
+        public
+        MethodHandle findStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+            Method method = refc.getDeclaredMethod(name, type.ptypes());
+            final int modifiers = method.getModifiers();
+            if (!Modifier.isStatic(modifiers)) {
+                throw new IllegalAccessException("Method" + method + " is not static");
+            }
+            checkReturnType(method, type);
+            checkAccess(refc, method.getDeclaringClass(), modifiers, method.getName());
+            return createMethodHandle(method, MethodHandle.INVOKE_STATIC, type);
+        }
+
+        private MethodHandle findVirtualForMH(String name, MethodType type) {
+            // these names require special lookups because of the implicit MethodType argument
+            if ("invoke".equals(name))
+                return invoker(type);
+            if ("invokeExact".equals(name))
+                return exactInvoker(type);
+            return null;
+        }
+
+        private static MethodHandle createMethodHandle(Method method, int handleKind,
+                                                       MethodType methodType) {
+            MethodHandle mh = new MethodHandleImpl(method.getArtMethod(), handleKind, methodType);
+            if (method.isVarArgs()) {
+                return new Transformers.VarargsCollector(mh);
+            } else {
+                return mh;
+            }
+        }
+
+        /**
+         * Produces a method handle for a virtual method.
+         * The type of the method handle will be that of the method,
+         * with the receiver type (usually {@code refc}) prepended.
+         * The method and all its argument types must be accessible to the lookup object.
+         * <p>
+         * When called, the handle will treat the first argument as a receiver
+         * and dispatch on the receiver's type to determine which method
+         * implementation to enter.
+         * (The dispatching action is identical with that performed by an
+         * {@code invokevirtual} or {@code invokeinterface} instruction.)
+         * <p>
+         * The first argument will be of type {@code refc} if the lookup
+         * class has full privileges to access the member.  Otherwise
+         * the member must be {@code protected} and the first argument
+         * will be restricted in type to the lookup class.
+         * <p>
+         * The returned method handle will have
+         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+         * the method's variable arity modifier bit ({@code 0x0080}) is set.
+         * <p>
+         * Because of the general <a href="MethodHandles.Lookup.html#equiv">equivalence</a> between {@code invokevirtual}
+         * instructions and method handles produced by {@code findVirtual},
+         * if the class is {@code MethodHandle} and the name string is
+         * {@code invokeExact} or {@code invoke}, the resulting
+         * method handle is equivalent to one produced by
+         * {@link java.lang.invoke.MethodHandles#exactInvoker MethodHandles.exactInvoker} or
+         * {@link java.lang.invoke.MethodHandles#invoker MethodHandles.invoker}
+         * with the same {@code type} argument.
+         *
+         * <b>Example:</b>
+         * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_concat = publicLookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+MethodHandle MH_hashCode = publicLookup().findVirtual(Object.class,
+  "hashCode", methodType(int.class));
+MethodHandle MH_hashCode_String = publicLookup().findVirtual(String.class,
+  "hashCode", methodType(int.class));
+assertEquals("xy", (String) MH_concat.invokeExact("x", "y"));
+assertEquals("xy".hashCode(), (int) MH_hashCode.invokeExact((Object)"xy"));
+assertEquals("xy".hashCode(), (int) MH_hashCode_String.invokeExact("xy"));
+// interface method:
+MethodHandle MH_subSequence = publicLookup().findVirtual(CharSequence.class,
+  "subSequence", methodType(CharSequence.class, int.class, int.class));
+assertEquals("def", MH_subSequence.invoke("abcdefghi", 3, 6).toString());
+// constructor "internal method" must be accessed differently:
+MethodType MT_newString = methodType(void.class); //()V for new String()
+try { assertEquals("impossible", lookup()
+        .findVirtual(String.class, "<init>", MT_newString));
+ } catch (NoSuchMethodException ex) { } // OK
+MethodHandle MH_newString = publicLookup()
+  .findConstructor(String.class, MT_newString);
+assertEquals("", (String) MH_newString.invokeExact());
+         * }</pre></blockquote>
+         *
+         * @param refc the class or interface from which the method is accessed
+         * @param name the name of the method
+         * @param type the type of the method, with the receiver argument omitted
+         * @return the desired method handle
+         * @throws NoSuchMethodException if the method does not exist
+         * @throws IllegalAccessException if access checking fails,
+         *                                or if the method is {@code static}
+         *                                or if the method's variable arity modifier bit
+         *                                is set and {@code asVarargsCollector} fails
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         */
+        public MethodHandle findVirtual(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+            // Special case : when we're looking up a virtual method on the MethodHandles class
+            // itself, we can return one of our specialized invokers.
+            if (refc == MethodHandle.class) {
+                MethodHandle mh = findVirtualForMH(name, type);
+                if (mh != null) {
+                    return mh;
+                }
+            }
+            // BEGIN Android-changed: Added VarHandle case here.
+            // Implementation to follow. TODO(b/65872996)
+            if (refc == VarHandle.class) {
+                unsupported("MethodHandles.findVirtual with refc == VarHandle.class");
+                return null;
+            }
+            // END Android-changed: Added VarHandle handling here.
+
+            Method method = refc.getInstanceMethod(name, type.ptypes());
+            if (method == null) {
+                // This is pretty ugly and a consequence of the MethodHandles API. We have to throw
+                // an IAE and not an NSME if the method exists but is static (even though the RI's
+                // IAE has a message that says "no such method"). We confine the ugliness and
+                // slowness to the failure case, and allow getInstanceMethod to remain fairly
+                // general.
+                try {
+                    Method m = refc.getDeclaredMethod(name, type.ptypes());
+                    if (Modifier.isStatic(m.getModifiers())) {
+                        throw new IllegalAccessException("Method" + m + " is static");
+                    }
+                } catch (NoSuchMethodException ignored) {
+                }
+
+                throw new NoSuchMethodException(name + " "  + Arrays.toString(type.ptypes()));
+            }
+            checkReturnType(method, type);
+
+            // We have a valid method, perform access checks.
+            checkAccess(refc, method.getDeclaringClass(), method.getModifiers(), method.getName());
+
+            // Insert the leading reference parameter.
+            MethodType handleType = type.insertParameterTypes(0, refc);
+            return createMethodHandle(method, MethodHandle.INVOKE_VIRTUAL, handleType);
+        }
+
+        /**
+         * Produces a method handle which creates an object and initializes it, using
+         * the constructor of the specified type.
+         * The parameter types of the method handle will be those of the constructor,
+         * while the return type will be a reference to the constructor's class.
+         * The constructor and all its argument types must be accessible to the lookup object.
+         * <p>
+         * The requested type must have a return type of {@code void}.
+         * (This is consistent with the JVM's treatment of constructor type descriptors.)
+         * <p>
+         * The returned method handle will have
+         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+         * the constructor's variable arity modifier bit ({@code 0x0080}) is set.
+         * <p>
+         * If the returned method handle is invoked, the constructor's class will
+         * be initialized, if it has not already been initialized.
+         * <p><b>Example:</b>
+         * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle MH_newArrayList = publicLookup().findConstructor(
+  ArrayList.class, methodType(void.class, Collection.class));
+Collection orig = Arrays.asList("x", "y");
+Collection copy = (ArrayList) MH_newArrayList.invokeExact(orig);
+assert(orig != copy);
+assertEquals(orig, copy);
+// a variable-arity constructor:
+MethodHandle MH_newProcessBuilder = publicLookup().findConstructor(
+  ProcessBuilder.class, methodType(void.class, String[].class));
+ProcessBuilder pb = (ProcessBuilder)
+  MH_newProcessBuilder.invoke("x", "y", "z");
+assertEquals("[x, y, z]", pb.command().toString());
+         * }</pre></blockquote>
+         * @param refc the class or interface from which the method is accessed
+         * @param type the type of the method, with the receiver argument omitted, and a void return type
+         * @return the desired method handle
+         * @throws NoSuchMethodException if the constructor does not exist
+         * @throws IllegalAccessException if access checking fails
+         *                                or if the method's variable arity modifier bit
+         *                                is set and {@code asVarargsCollector} fails
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         */
+        public MethodHandle findConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+            if (refc.isArray()) {
+                throw new NoSuchMethodException("no constructor for array class: " + refc.getName());
+            }
+            // The queried |type| is (PT1,PT2,..)V
+            Constructor constructor = refc.getDeclaredConstructor(type.ptypes());
+            if (constructor == null) {
+                throw new NoSuchMethodException(
+                    "No constructor for " + constructor.getDeclaringClass() + " matching " + type);
+            }
+            checkAccess(refc, constructor.getDeclaringClass(), constructor.getModifiers(),
+                    constructor.getName());
+
+            return createMethodHandleForConstructor(constructor);
+        }
+
+        private MethodHandle createMethodHandleForConstructor(Constructor constructor) {
+            Class<?> refc = constructor.getDeclaringClass();
+            MethodType constructorType =
+                    MethodType.methodType(refc, constructor.getParameterTypes());
+            MethodHandle mh;
+            if (refc == String.class) {
+                // String constructors have optimized StringFactory methods
+                // that matches returned type. These factory methods combine the
+                // memory allocation and initialization calls for String objects.
+                mh = new MethodHandleImpl(constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT,
+                                          constructorType);
+            } else {
+                // Constructors for all other classes use a Construct transformer to perform
+                // their memory allocation and call to <init>.
+                MethodType initType = initMethodType(constructorType);
+                MethodHandle initHandle = new MethodHandleImpl(
+                    constructor.getArtMethod(), MethodHandle.INVOKE_DIRECT, initType);
+                mh = new Transformers.Construct(initHandle, constructorType);
+            }
+
+            if (constructor.isVarArgs()) {
+                mh = new Transformers.VarargsCollector(mh);
+            }
+            return mh;
+        }
+
+        private static MethodType initMethodType(MethodType constructorType) {
+            // Returns a MethodType appropriate for class <init>
+            // methods. Constructor MethodTypes have the form
+            // (PT1,PT2,...)C and class <init> MethodTypes have the
+            // form (C,PT1,PT2,...)V.
+            assert constructorType.rtype() != void.class;
+
+            // Insert constructorType C as the first parameter type in
+            // the MethodType for <init>.
+            Class<?> [] initPtypes = new Class<?> [constructorType.ptypes().length + 1];
+            initPtypes[0] = constructorType.rtype();
+            System.arraycopy(constructorType.ptypes(), 0, initPtypes, 1,
+                             constructorType.ptypes().length);
+
+            // Set the return type for the <init> MethodType to be void.
+            return MethodType.methodType(void.class, initPtypes);
+        }
+
+        /**
+         * Produces an early-bound method handle for a virtual method.
+         * It will bypass checks for overriding methods on the receiver,
+         * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial}
+         * instruction from within the explicitly specified {@code specialCaller}.
+         * The type of the method handle will be that of the method,
+         * with a suitably restricted receiver type prepended.
+         * (The receiver type will be {@code specialCaller} or a subtype.)
+         * The method and all its argument types must be accessible
+         * to the lookup object.
+         * <p>
+         * Before method resolution,
+         * if the explicitly specified caller class is not identical with the
+         * lookup class, or if this lookup object does not have
+         * <a href="MethodHandles.Lookup.html#privacc">private access</a>
+         * privileges, the access fails.
+         * <p>
+         * The returned method handle will have
+         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+         * the method's variable arity modifier bit ({@code 0x0080}) is set.
+         * <p style="font-size:smaller;">
+         * <em>(Note:  JVM internal methods named {@code "<init>"} are not visible to this API,
+         * even though the {@code invokespecial} instruction can refer to them
+         * in special circumstances.  Use {@link #findConstructor findConstructor}
+         * to access instance initialization methods in a safe manner.)</em>
+         * <p><b>Example:</b>
+         * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+static class Listie extends ArrayList {
+  public String toString() { return "[wee Listie]"; }
+  static Lookup lookup() { return MethodHandles.lookup(); }
+}
+...
+// no access to constructor via invokeSpecial:
+MethodHandle MH_newListie = Listie.lookup()
+  .findConstructor(Listie.class, methodType(void.class));
+Listie l = (Listie) MH_newListie.invokeExact();
+try { assertEquals("impossible", Listie.lookup().findSpecial(
+        Listie.class, "<init>", methodType(void.class), Listie.class));
+ } catch (NoSuchMethodException ex) { } // OK
+// access to super and self methods via invokeSpecial:
+MethodHandle MH_super = Listie.lookup().findSpecial(
+  ArrayList.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_this = Listie.lookup().findSpecial(
+  Listie.class, "toString" , methodType(String.class), Listie.class);
+MethodHandle MH_duper = Listie.lookup().findSpecial(
+  Object.class, "toString" , methodType(String.class), Listie.class);
+assertEquals("[]", (String) MH_super.invokeExact(l));
+assertEquals(""+l, (String) MH_this.invokeExact(l));
+assertEquals("[]", (String) MH_duper.invokeExact(l)); // ArrayList method
+try { assertEquals("inaccessible", Listie.lookup().findSpecial(
+        String.class, "toString", methodType(String.class), Listie.class));
+ } catch (IllegalAccessException ex) { } // OK
+Listie subl = new Listie() { public String toString() { return "[subclass]"; } };
+assertEquals(""+l, (String) MH_this.invokeExact(subl)); // Listie method
+         * }</pre></blockquote>
+         *
+         * @param refc the class or interface from which the method is accessed
+         * @param name the name of the method (which must not be "&lt;init&gt;")
+         * @param type the type of the method, with the receiver argument omitted
+         * @param specialCaller the proposed calling class to perform the {@code invokespecial}
+         * @return the desired method handle
+         * @throws NoSuchMethodException if the method does not exist
+         * @throws IllegalAccessException if access checking fails
+         *                                or if the method's variable arity modifier bit
+         *                                is set and {@code asVarargsCollector} fails
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         */
+        public MethodHandle findSpecial(Class<?> refc, String name, MethodType type,
+                                        Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
+            if (specialCaller == null) {
+                throw new NullPointerException("specialCaller == null");
+            }
+
+            if (type == null) {
+                throw new NullPointerException("type == null");
+            }
+
+            if (name == null) {
+                throw new NullPointerException("name == null");
+            }
+
+            if (refc == null) {
+                throw new NullPointerException("ref == null");
+            }
+
+            // Make sure that the special caller is identical to the lookup class or that we have
+            // private access.
+            checkSpecialCaller(specialCaller);
+
+            // Even though constructors are invoked using a "special" invoke, handles to them can't
+            // be created using findSpecial. Callers must use findConstructor instead. Similarly,
+            // there is no path for calling static class initializers.
+            if (name.startsWith("<")) {
+                throw new NoSuchMethodException(name + " is not a valid method name.");
+            }
+
+            Method method = refc.getDeclaredMethod(name, type.ptypes());
+            checkReturnType(method, type);
+            return findSpecial(method, type, refc, specialCaller);
+        }
+
+        private MethodHandle findSpecial(Method method, MethodType type,
+                                         Class<?> refc, Class<?> specialCaller)
+                throws IllegalAccessException {
+            if (Modifier.isStatic(method.getModifiers())) {
+                throw new IllegalAccessException("expected a non-static method:" + method);
+            }
+
+            if (Modifier.isPrivate(method.getModifiers())) {
+                // Since this is a private method, we'll need to also make sure that the
+                // lookup class is the same as the refering class. We've already checked that
+                // the specialCaller is the same as the special lookup class, both of these must
+                // be the same as the declaring class(*) in order to access the private method.
+                //
+                // (*) Well, this isn't true for nested classes but OpenJDK doesn't support those
+                // either.
+                if (refc != lookupClass()) {
+                    throw new IllegalAccessException("no private access for invokespecial : "
+                            + refc + ", from" + this);
+                }
+
+                // This is a private method, so there's nothing special to do.
+                MethodType handleType = type.insertParameterTypes(0, refc);
+                return createMethodHandle(method, MethodHandle.INVOKE_DIRECT, handleType);
+            }
+
+            // This is a public, protected or package-private method, which means we're expecting
+            // invoke-super semantics. We'll have to restrict the receiver type appropriately on the
+            // handle once we check that there really is a "super" relationship between them.
+            if (!method.getDeclaringClass().isAssignableFrom(specialCaller)) {
+                throw new IllegalAccessException(refc + "is not assignable from " + specialCaller);
+            }
+
+            // Note that we restrict the receiver to "specialCaller" instances.
+            MethodType handleType = type.insertParameterTypes(0, specialCaller);
+            return createMethodHandle(method, MethodHandle.INVOKE_SUPER, handleType);
+        }
+
+        /**
+         * Produces a method handle giving read access to a non-static field.
+         * The type of the method handle will have a return type of the field's
+         * value type.
+         * The method handle's single argument will be the instance containing
+         * the field.
+         * Access checking is performed immediately on behalf of the lookup class.
+         * @param refc the class or interface from which the method is accessed
+         * @param name the field's name
+         * @param type the field's type
+         * @return a method handle which can load values from the field
+         * @throws NoSuchFieldException if the field does not exist
+         * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         */
+        public MethodHandle findGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+            return findAccessor(refc, name, type, MethodHandle.IGET);
+        }
+
+        private MethodHandle findAccessor(Class<?> refc, String name, Class<?> type, int kind)
+            throws NoSuchFieldException, IllegalAccessException {
+            final Field field = findFieldOfType(refc, name, type);
+            return findAccessor(field, refc, type, kind, true /* performAccessChecks */);
+        }
+
+        private MethodHandle findAccessor(Field field, Class<?> refc, Class<?> type, int kind,
+                                          boolean performAccessChecks)
+                throws IllegalAccessException {
+            final boolean isSetterKind = kind == MethodHandle.IPUT || kind == MethodHandle.SPUT;
+            final boolean isStaticKind = kind == MethodHandle.SGET || kind == MethodHandle.SPUT;
+            commonFieldChecks(field, refc, type, isStaticKind, performAccessChecks);
+            if (performAccessChecks) {
+                final int modifiers = field.getModifiers();
+                if (isSetterKind && Modifier.isFinal(modifiers)) {
+                    throw new IllegalAccessException("Field " + field + " is final");
+                }
+            }
+
+            final MethodType methodType;
+            switch (kind) {
+                case MethodHandle.SGET:
+                    methodType = MethodType.methodType(type);
+                    break;
+                case MethodHandle.SPUT:
+                    methodType = MethodType.methodType(void.class, type);
+                    break;
+                case MethodHandle.IGET:
+                    methodType = MethodType.methodType(type, refc);
+                    break;
+                case MethodHandle.IPUT:
+                    methodType = MethodType.methodType(void.class, refc, type);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Invalid kind " + kind);
+            }
+            return new MethodHandleImpl(field.getArtField(), kind, methodType);
+        }
+
+        /**
+         * Produces a method handle giving write access to a non-static field.
+         * The type of the method handle will have a void return type.
+         * The method handle will take two arguments, the instance containing
+         * the field, and the value to be stored.
+         * The second argument will be of the field's value type.
+         * Access checking is performed immediately on behalf of the lookup class.
+         * @param refc the class or interface from which the method is accessed
+         * @param name the field's name
+         * @param type the field's type
+         * @return a method handle which can store values into the field
+         * @throws NoSuchFieldException if the field does not exist
+         * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         */
+        public MethodHandle findSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+            return findAccessor(refc, name, type, MethodHandle.IPUT);
+        }
+
+        // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method.
+        /**
+         * Produces a VarHandle giving access to a non-static field {@code name}
+         * of type {@code type} declared in a class of type {@code recv}.
+         * The VarHandle's variable type is {@code type} and it has one
+         * coordinate type, {@code recv}.
+         * <p>
+         * Access checking is performed immediately on behalf of the lookup
+         * class.
+         * <p>
+         * Certain access modes of the returned VarHandle are unsupported under
+         * the following conditions:
+         * <ul>
+         * <li>if the field is declared {@code final}, then the write, atomic
+         *     update, numeric atomic update, and bitwise atomic update access
+         *     modes are unsupported.
+         * <li>if the field type is anything other than {@code byte},
+         *     {@code short}, {@code char}, {@code int}, {@code long},
+         *     {@code float}, or {@code double} then numeric atomic update
+         *     access modes are unsupported.
+         * <li>if the field type is anything other than {@code boolean},
+         *     {@code byte}, {@code short}, {@code char}, {@code int} or
+         *     {@code long} then bitwise atomic update access modes are
+         *     unsupported.
+         * </ul>
+         * <p>
+         * If the field is declared {@code volatile} then the returned VarHandle
+         * will override access to the field (effectively ignore the
+         * {@code volatile} declaration) in accordance to its specified
+         * access modes.
+         * <p>
+         * If the field type is {@code float} or {@code double} then numeric
+         * and atomic update access modes compare values using their bitwise
+         * representation (see {@link Float#floatToRawIntBits} and
+         * {@link Double#doubleToRawLongBits}, respectively).
+         * @apiNote
+         * Bitwise comparison of {@code float} values or {@code double} values,
+         * as performed by the numeric and atomic update access modes, differ
+         * from the primitive {@code ==} operator and the {@link Float#equals}
+         * and {@link Double#equals} methods, specifically with respect to
+         * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+         * Care should be taken when performing a compare and set or a compare
+         * and exchange operation with such values since the operation may
+         * unexpectedly fail.
+         * There are many possible NaN values that are considered to be
+         * {@code NaN} in Java, although no IEEE 754 floating-point operation
+         * provided by Java can distinguish between them.  Operation failure can
+         * occur if the expected or witness value is a NaN value and it is
+         * transformed (perhaps in a platform specific manner) into another NaN
+         * value, and thus has a different bitwise representation (see
+         * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+         * details).
+         * The values {@code -0.0} and {@code +0.0} have different bitwise
+         * representations but are considered equal when using the primitive
+         * {@code ==} operator.  Operation failure can occur if, for example, a
+         * numeric algorithm computes an expected value to be say {@code -0.0}
+         * and previously computed the witness value to be say {@code +0.0}.
+         * @param recv the receiver class, of type {@code R}, that declares the
+         * non-static field
+         * @param name the field's name
+         * @param type the field's type, of type {@code T}
+         * @return a VarHandle giving access to non-static fields.
+         * @throws NoSuchFieldException if the field does not exist
+         * @throws IllegalAccessException if access checking fails, or if the field is {@code static}
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         * @since 9
+         * @hide
+         */
+        public VarHandle findVarHandle(Class<?> recv, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+            final Field field = findFieldOfType(recv, name, type);
+            final boolean isStatic = false;
+            final boolean performAccessChecks = true;
+            commonFieldChecks(field, recv, type, isStatic, performAccessChecks);
+            return FieldVarHandle.create(field);
+        }
+        // END Android-changed: OpenJDK 9+181 VarHandle API factory method.
+
+        // BEGIN Android-added: Common field resolution and access check methods.
+        private Field findFieldOfType(final Class<?> refc, String name, Class<?> type)
+                throws NoSuchFieldException {
+            Field field = null;
+
+            // Search refc and super classes for the field.
+            for (Class<?> cls = refc; cls != null; cls = cls.getSuperclass()) {
+                try {
+                    field = cls.getDeclaredField(name);
+                    break;
+                } catch (NoSuchFieldException e) {
+                }
+            }
+
+            if (field == null) {
+                // Force failure citing refc.
+                field = refc.getDeclaredField(name);
+            }
+
+            final Class<?> fieldType = field.getType();
+            if (fieldType != type) {
+                throw new NoSuchFieldException(name);
+            }
+            return field;
+        }
+
+        private void commonFieldChecks(Field field, Class<?> refc, Class<?> type,
+                                       boolean isStatic, boolean performAccessChecks)
+                throws IllegalAccessException {
+            final int modifiers = field.getModifiers();
+            if (performAccessChecks) {
+                checkAccess(refc, field.getDeclaringClass(), modifiers, field.getName());
+            }
+            if (Modifier.isStatic(modifiers) != isStatic) {
+                String reason = "Field " + field + " is " +
+                        (isStatic ? "not " : "") + "static";
+                throw new IllegalAccessException(reason);
+            }
+        }
+        // END Android-added: Common field resolution and access check methods.
+
+        /**
+         * Produces a method handle giving read access to a static field.
+         * The type of the method handle will have a return type of the field's
+         * value type.
+         * The method handle will take no arguments.
+         * Access checking is performed immediately on behalf of the lookup class.
+         * <p>
+         * If the returned method handle is invoked, the field's class will
+         * be initialized, if it has not already been initialized.
+         * @param refc the class or interface from which the method is accessed
+         * @param name the field's name
+         * @param type the field's type
+         * @return a method handle which can load values from the field
+         * @throws NoSuchFieldException if the field does not exist
+         * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         */
+        public MethodHandle findStaticGetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+            return findAccessor(refc, name, type, MethodHandle.SGET);
+        }
+
+        /**
+         * Produces a method handle giving write access to a static field.
+         * The type of the method handle will have a void return type.
+         * The method handle will take a single
+         * argument, of the field's value type, the value to be stored.
+         * Access checking is performed immediately on behalf of the lookup class.
+         * <p>
+         * If the returned method handle is invoked, the field's class will
+         * be initialized, if it has not already been initialized.
+         * @param refc the class or interface from which the method is accessed
+         * @param name the field's name
+         * @param type the field's type
+         * @return a method handle which can store values into the field
+         * @throws NoSuchFieldException if the field does not exist
+         * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         */
+        public MethodHandle findStaticSetter(Class<?> refc, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+            return findAccessor(refc, name, type, MethodHandle.SPUT);
+        }
+
+        // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method.
+        /**
+         * Produces a VarHandle giving access to a static field {@code name} of
+         * type {@code type} declared in a class of type {@code decl}.
+         * The VarHandle's variable type is {@code type} and it has no
+         * coordinate types.
+         * <p>
+         * Access checking is performed immediately on behalf of the lookup
+         * class.
+         * <p>
+         * If the returned VarHandle is operated on, the declaring class will be
+         * initialized, if it has not already been initialized.
+         * <p>
+         * Certain access modes of the returned VarHandle are unsupported under
+         * the following conditions:
+         * <ul>
+         * <li>if the field is declared {@code final}, then the write, atomic
+         *     update, numeric atomic update, and bitwise atomic update access
+         *     modes are unsupported.
+         * <li>if the field type is anything other than {@code byte},
+         *     {@code short}, {@code char}, {@code int}, {@code long},
+         *     {@code float}, or {@code double}, then numeric atomic update
+         *     access modes are unsupported.
+         * <li>if the field type is anything other than {@code boolean},
+         *     {@code byte}, {@code short}, {@code char}, {@code int} or
+         *     {@code long} then bitwise atomic update access modes are
+         *     unsupported.
+         * </ul>
+         * <p>
+         * If the field is declared {@code volatile} then the returned VarHandle
+         * will override access to the field (effectively ignore the
+         * {@code volatile} declaration) in accordance to its specified
+         * access modes.
+         * <p>
+         * If the field type is {@code float} or {@code double} then numeric
+         * and atomic update access modes compare values using their bitwise
+         * representation (see {@link Float#floatToRawIntBits} and
+         * {@link Double#doubleToRawLongBits}, respectively).
+         * @apiNote
+         * Bitwise comparison of {@code float} values or {@code double} values,
+         * as performed by the numeric and atomic update access modes, differ
+         * from the primitive {@code ==} operator and the {@link Float#equals}
+         * and {@link Double#equals} methods, specifically with respect to
+         * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+         * Care should be taken when performing a compare and set or a compare
+         * and exchange operation with such values since the operation may
+         * unexpectedly fail.
+         * There are many possible NaN values that are considered to be
+         * {@code NaN} in Java, although no IEEE 754 floating-point operation
+         * provided by Java can distinguish between them.  Operation failure can
+         * occur if the expected or witness value is a NaN value and it is
+         * transformed (perhaps in a platform specific manner) into another NaN
+         * value, and thus has a different bitwise representation (see
+         * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+         * details).
+         * The values {@code -0.0} and {@code +0.0} have different bitwise
+         * representations but are considered equal when using the primitive
+         * {@code ==} operator.  Operation failure can occur if, for example, a
+         * numeric algorithm computes an expected value to be say {@code -0.0}
+         * and previously computed the witness value to be say {@code +0.0}.
+         * @param decl the class that declares the static field
+         * @param name the field's name
+         * @param type the field's type, of type {@code T}
+         * @return a VarHandle giving access to a static field
+         * @throws NoSuchFieldException if the field does not exist
+         * @throws IllegalAccessException if access checking fails, or if the field is not {@code static}
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         * @since 9
+         * @hide
+         */
+        public VarHandle findStaticVarHandle(Class<?> decl, String name, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
+            final Field field = findFieldOfType(decl, name, type);
+            final boolean isStatic = true;
+            final boolean performAccessChecks = true;
+            commonFieldChecks(field, decl, type, isStatic, performAccessChecks);
+            return FieldVarHandle.create(field);
+        }
+        // END Android-changed: OpenJDK 9+181 VarHandle API factory method.
+
+        /**
+         * Produces an early-bound method handle for a non-static method.
+         * The receiver must have a supertype {@code defc} in which a method
+         * of the given name and type is accessible to the lookup class.
+         * The method and all its argument types must be accessible to the lookup object.
+         * The type of the method handle will be that of the method,
+         * without any insertion of an additional receiver parameter.
+         * The given receiver will be bound into the method handle,
+         * so that every call to the method handle will invoke the
+         * requested method on the given receiver.
+         * <p>
+         * The returned method handle will have
+         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+         * the method's variable arity modifier bit ({@code 0x0080}) is set
+         * <em>and</em> the trailing array argument is not the only argument.
+         * (If the trailing array argument is the only argument,
+         * the given receiver value will be bound to it.)
+         * <p>
+         * This is equivalent to the following code:
+         * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle mh0 = lookup().findVirtual(defc, name, type);
+MethodHandle mh1 = mh0.bindTo(receiver);
+MethodType mt1 = mh1.type();
+if (mh0.isVarargsCollector())
+  mh1 = mh1.asVarargsCollector(mt1.parameterType(mt1.parameterCount()-1));
+return mh1;
+         * }</pre></blockquote>
+         * where {@code defc} is either {@code receiver.getClass()} or a super
+         * type of that class, in which the requested method is accessible
+         * to the lookup class.
+         * (Note that {@code bindTo} does not preserve variable arity.)
+         * @param receiver the object from which the method is accessed
+         * @param name the name of the method
+         * @param type the type of the method, with the receiver argument omitted
+         * @return the desired method handle
+         * @throws NoSuchMethodException if the method does not exist
+         * @throws IllegalAccessException if access checking fails
+         *                                or if the method's variable arity modifier bit
+         *                                is set and {@code asVarargsCollector} fails
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws NullPointerException if any argument is null
+         * @see MethodHandle#bindTo
+         * @see #findVirtual
+         */
+        public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
+            MethodHandle handle = findVirtual(receiver.getClass(), name, type);
+            MethodHandle adapter = handle.bindTo(receiver);
+            MethodType adapterType = adapter.type();
+            if (handle.isVarargsCollector()) {
+                adapter = adapter.asVarargsCollector(
+                        adapterType.parameterType(adapterType.parameterCount() - 1));
+            }
+
+            return adapter;
+        }
+
+        /**
+         * Makes a <a href="MethodHandleInfo.html#directmh">direct method handle</a>
+         * to <i>m</i>, if the lookup class has permission.
+         * If <i>m</i> is non-static, the receiver argument is treated as an initial argument.
+         * If <i>m</i> is virtual, overriding is respected on every call.
+         * Unlike the Core Reflection API, exceptions are <em>not</em> wrapped.
+         * The type of the method handle will be that of the method,
+         * with the receiver type prepended (but only if it is non-static).
+         * If the method's {@code accessible} flag is not set,
+         * access checking is performed immediately on behalf of the lookup class.
+         * If <i>m</i> is not public, do not share the resulting handle with untrusted parties.
+         * <p>
+         * The returned method handle will have
+         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+         * the method's variable arity modifier bit ({@code 0x0080}) is set.
+         * <p>
+         * If <i>m</i> is static, and
+         * if the returned method handle is invoked, the method's class will
+         * be initialized, if it has not already been initialized.
+         * @param m the reflected method
+         * @return a method handle which can invoke the reflected method
+         * @throws IllegalAccessException if access checking fails
+         *                                or if the method's variable arity modifier bit
+         *                                is set and {@code asVarargsCollector} fails
+         * @throws NullPointerException if the argument is null
+         */
+        public MethodHandle unreflect(Method m) throws IllegalAccessException {
+            if (m == null) {
+                throw new NullPointerException("m == null");
+            }
+
+            MethodType methodType = MethodType.methodType(m.getReturnType(),
+                    m.getParameterTypes());
+
+            // We should only perform access checks if setAccessible hasn't been called yet.
+            if (!m.isAccessible()) {
+                checkAccess(m.getDeclaringClass(), m.getDeclaringClass(), m.getModifiers(),
+                        m.getName());
+            }
+
+            if (Modifier.isStatic(m.getModifiers())) {
+                return createMethodHandle(m, MethodHandle.INVOKE_STATIC, methodType);
+            } else {
+                methodType = methodType.insertParameterTypes(0, m.getDeclaringClass());
+                return createMethodHandle(m, MethodHandle.INVOKE_VIRTUAL, methodType);
+            }
+        }
+
+        /**
+         * Produces a method handle for a reflected method.
+         * It will bypass checks for overriding methods on the receiver,
+         * <a href="MethodHandles.Lookup.html#equiv">as if called</a> from an {@code invokespecial}
+         * instruction from within the explicitly specified {@code specialCaller}.
+         * The type of the method handle will be that of the method,
+         * with a suitably restricted receiver type prepended.
+         * (The receiver type will be {@code specialCaller} or a subtype.)
+         * If the method's {@code accessible} flag is not set,
+         * access checking is performed immediately on behalf of the lookup class,
+         * as if {@code invokespecial} instruction were being linked.
+         * <p>
+         * Before method resolution,
+         * if the explicitly specified caller class is not identical with the
+         * lookup class, or if this lookup object does not have
+         * <a href="MethodHandles.Lookup.html#privacc">private access</a>
+         * privileges, the access fails.
+         * <p>
+         * The returned method handle will have
+         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+         * the method's variable arity modifier bit ({@code 0x0080}) is set.
+         * @param m the reflected method
+         * @param specialCaller the class nominally calling the method
+         * @return a method handle which can invoke the reflected method
+         * @throws IllegalAccessException if access checking fails
+         *                                or if the method's variable arity modifier bit
+         *                                is set and {@code asVarargsCollector} fails
+         * @throws NullPointerException if any argument is null
+         */
+        public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException {
+            if (m == null) {
+                throw new NullPointerException("m == null");
+            }
+
+            if (specialCaller == null) {
+                throw new NullPointerException("specialCaller == null");
+            }
+
+            if (!m.isAccessible()) {
+                checkSpecialCaller(specialCaller);
+            }
+
+            final MethodType methodType = MethodType.methodType(m.getReturnType(),
+                    m.getParameterTypes());
+            return findSpecial(m, methodType, m.getDeclaringClass() /* refc */, specialCaller);
+        }
+
+        /**
+         * Produces a method handle for a reflected constructor.
+         * The type of the method handle will be that of the constructor,
+         * with the return type changed to the declaring class.
+         * The method handle will perform a {@code newInstance} operation,
+         * creating a new instance of the constructor's class on the
+         * arguments passed to the method handle.
+         * <p>
+         * If the constructor's {@code accessible} flag is not set,
+         * access checking is performed immediately on behalf of the lookup class.
+         * <p>
+         * The returned method handle will have
+         * {@linkplain MethodHandle#asVarargsCollector variable arity} if and only if
+         * the constructor's variable arity modifier bit ({@code 0x0080}) is set.
+         * <p>
+         * If the returned method handle is invoked, the constructor's class will
+         * be initialized, if it has not already been initialized.
+         * @param c the reflected constructor
+         * @return a method handle which can invoke the reflected constructor
+         * @throws IllegalAccessException if access checking fails
+         *                                or if the method's variable arity modifier bit
+         *                                is set and {@code asVarargsCollector} fails
+         * @throws NullPointerException if the argument is null
+         */
+        public MethodHandle unreflectConstructor(Constructor<?> c) throws IllegalAccessException {
+            if (c == null) {
+                throw new NullPointerException("c == null");
+            }
+
+            if (!c.isAccessible()) {
+                checkAccess(c.getDeclaringClass(), c.getDeclaringClass(), c.getModifiers(),
+                        c.getName());
+            }
+
+            return createMethodHandleForConstructor(c);
+        }
+
+        /**
+         * Produces a method handle giving read access to a reflected field.
+         * The type of the method handle will have a return type of the field's
+         * value type.
+         * If the field is static, the method handle will take no arguments.
+         * Otherwise, its single argument will be the instance containing
+         * the field.
+         * If the field's {@code accessible} flag is not set,
+         * access checking is performed immediately on behalf of the lookup class.
+         * <p>
+         * If the field is static, and
+         * if the returned method handle is invoked, the field's class will
+         * be initialized, if it has not already been initialized.
+         * @param f the reflected field
+         * @return a method handle which can load values from the reflected field
+         * @throws IllegalAccessException if access checking fails
+         * @throws NullPointerException if the argument is null
+         */
+        public MethodHandle unreflectGetter(Field f) throws IllegalAccessException {
+            return findAccessor(f, f.getDeclaringClass(), f.getType(),
+                    Modifier.isStatic(f.getModifiers()) ? MethodHandle.SGET : MethodHandle.IGET,
+                    !f.isAccessible() /* performAccessChecks */);
+        }
+
+        /**
+         * Produces a method handle giving write access to a reflected field.
+         * The type of the method handle will have a void return type.
+         * If the field is static, the method handle will take a single
+         * argument, of the field's value type, the value to be stored.
+         * Otherwise, the two arguments will be the instance containing
+         * the field, and the value to be stored.
+         * If the field's {@code accessible} flag is not set,
+         * access checking is performed immediately on behalf of the lookup class.
+         * <p>
+         * If the field is static, and
+         * if the returned method handle is invoked, the field's class will
+         * be initialized, if it has not already been initialized.
+         * @param f the reflected field
+         * @return a method handle which can store values into the reflected field
+         * @throws IllegalAccessException if access checking fails
+         * @throws NullPointerException if the argument is null
+         */
+        public MethodHandle unreflectSetter(Field f) throws IllegalAccessException {
+            return findAccessor(f, f.getDeclaringClass(), f.getType(),
+                    Modifier.isStatic(f.getModifiers()) ? MethodHandle.SPUT : MethodHandle.IPUT,
+                    !f.isAccessible() /* performAccessChecks */);
+        }
+
+        // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory method.
+        /**
+         * Produces a VarHandle giving access to a reflected field {@code f}
+         * of type {@code T} declared in a class of type {@code R}.
+         * The VarHandle's variable type is {@code T}.
+         * If the field is non-static the VarHandle has one coordinate type,
+         * {@code R}.  Otherwise, the field is static, and the VarHandle has no
+         * coordinate types.
+         * <p>
+         * Access checking is performed immediately on behalf of the lookup
+         * class, regardless of the value of the field's {@code accessible}
+         * flag.
+         * <p>
+         * If the field is static, and if the returned VarHandle is operated
+         * on, the field's declaring class will be initialized, if it has not
+         * already been initialized.
+         * <p>
+         * Certain access modes of the returned VarHandle are unsupported under
+         * the following conditions:
+         * <ul>
+         * <li>if the field is declared {@code final}, then the write, atomic
+         *     update, numeric atomic update, and bitwise atomic update access
+         *     modes are unsupported.
+         * <li>if the field type is anything other than {@code byte},
+         *     {@code short}, {@code char}, {@code int}, {@code long},
+         *     {@code float}, or {@code double} then numeric atomic update
+         *     access modes are unsupported.
+         * <li>if the field type is anything other than {@code boolean},
+         *     {@code byte}, {@code short}, {@code char}, {@code int} or
+         *     {@code long} then bitwise atomic update access modes are
+         *     unsupported.
+         * </ul>
+         * <p>
+         * If the field is declared {@code volatile} then the returned VarHandle
+         * will override access to the field (effectively ignore the
+         * {@code volatile} declaration) in accordance to its specified
+         * access modes.
+         * <p>
+         * If the field type is {@code float} or {@code double} then numeric
+         * and atomic update access modes compare values using their bitwise
+         * representation (see {@link Float#floatToRawIntBits} and
+         * {@link Double#doubleToRawLongBits}, respectively).
+         * @apiNote
+         * Bitwise comparison of {@code float} values or {@code double} values,
+         * as performed by the numeric and atomic update access modes, differ
+         * from the primitive {@code ==} operator and the {@link Float#equals}
+         * and {@link Double#equals} methods, specifically with respect to
+         * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+         * Care should be taken when performing a compare and set or a compare
+         * and exchange operation with such values since the operation may
+         * unexpectedly fail.
+         * There are many possible NaN values that are considered to be
+         * {@code NaN} in Java, although no IEEE 754 floating-point operation
+         * provided by Java can distinguish between them.  Operation failure can
+         * occur if the expected or witness value is a NaN value and it is
+         * transformed (perhaps in a platform specific manner) into another NaN
+         * value, and thus has a different bitwise representation (see
+         * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+         * details).
+         * The values {@code -0.0} and {@code +0.0} have different bitwise
+         * representations but are considered equal when using the primitive
+         * {@code ==} operator.  Operation failure can occur if, for example, a
+         * numeric algorithm computes an expected value to be say {@code -0.0}
+         * and previously computed the witness value to be say {@code +0.0}.
+         * @param f the reflected field, with a field of type {@code T}, and
+         * a declaring class of type {@code R}
+         * @return a VarHandle giving access to non-static fields or a static
+         * field
+         * @throws IllegalAccessException if access checking fails
+         * @throws NullPointerException if the argument is null
+         * @since 9
+         * @hide
+         */
+        public VarHandle unreflectVarHandle(Field f) throws IllegalAccessException {
+            final boolean isStatic = Modifier.isStatic(f.getModifiers());
+            final boolean performAccessChecks = true;
+            commonFieldChecks(f, f.getDeclaringClass(), f.getType(), isStatic, performAccessChecks);
+            return FieldVarHandle.create(f);
+        }
+        // END Android-changed: OpenJDK 9+181 VarHandle API factory method.
+
+        /**
+         * Cracks a <a href="MethodHandleInfo.html#directmh">direct method handle</a>
+         * created by this lookup object or a similar one.
+         * Security and access checks are performed to ensure that this lookup object
+         * is capable of reproducing the target method handle.
+         * This means that the cracking may fail if target is a direct method handle
+         * but was created by an unrelated lookup object.
+         * This can happen if the method handle is <a href="MethodHandles.Lookup.html#callsens">caller sensitive</a>
+         * and was created by a lookup object for a different class.
+         * @param target a direct method handle to crack into symbolic reference components
+         * @return a symbolic reference which can be used to reconstruct this method handle from this lookup object
+         * @exception SecurityException if a security manager is present and it
+         *                              <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
+         * @throws IllegalArgumentException if the target is not a direct method handle or if access checking fails
+         * @exception NullPointerException if the target is {@code null}
+         * @see MethodHandleInfo
+         * @since 1.8
+         */
+        public MethodHandleInfo revealDirect(MethodHandle target) {
+            MethodHandleImpl directTarget = getMethodHandleImpl(target);
+            MethodHandleInfo info = directTarget.reveal();
+
+            try {
+                checkAccess(lookupClass(), info.getDeclaringClass(), info.getModifiers(),
+                        info.getName());
+            } catch (IllegalAccessException exception) {
+                throw new IllegalArgumentException("Unable to access memeber.", exception);
+            }
+
+            return info;
+        }
+
+        private boolean hasPrivateAccess() {
+            return (allowedModes & PRIVATE) != 0;
+        }
+
+        /** Check public/protected/private bits on the symbolic reference class and its member. */
+        void checkAccess(Class<?> refc, Class<?> defc, int mods, String methName)
+                throws IllegalAccessException {
+            int allowedModes = this.allowedModes;
+
+            if (Modifier.isProtected(mods) &&
+                    defc == Object.class &&
+                    "clone".equals(methName) &&
+                    refc.isArray()) {
+                // The JVM does this hack also.
+                // (See ClassVerifier::verify_invoke_instructions
+                // and LinkResolver::check_method_accessability.)
+                // Because the JVM does not allow separate methods on array types,
+                // there is no separate method for int[].clone.
+                // All arrays simply inherit Object.clone.
+                // But for access checking logic, we make Object.clone
+                // (normally protected) appear to be public.
+                // Later on, when the DirectMethodHandle is created,
+                // its leading argument will be restricted to the
+                // requested array type.
+                // N.B. The return type is not adjusted, because
+                // that is *not* the bytecode behavior.
+                mods ^= Modifier.PROTECTED | Modifier.PUBLIC;
+            }
+
+            if (Modifier.isProtected(mods) && Modifier.isConstructor(mods)) {
+                // cannot "new" a protected ctor in a different package
+                mods ^= Modifier.PROTECTED;
+            }
+
+            if (Modifier.isPublic(mods) && Modifier.isPublic(refc.getModifiers()) && allowedModes != 0)
+                return;  // common case
+            int requestedModes = fixmods(mods);  // adjust 0 => PACKAGE
+            if ((requestedModes & allowedModes) != 0) {
+                if (VerifyAccess.isMemberAccessible(refc, defc, mods, lookupClass(), allowedModes))
+                    return;
+            } else {
+                // Protected members can also be checked as if they were package-private.
+                if ((requestedModes & PROTECTED) != 0 && (allowedModes & PACKAGE) != 0
+                        && VerifyAccess.isSamePackage(defc, lookupClass()))
+                    return;
+            }
+
+            throwMakeAccessException(accessFailedMessage(refc, defc, mods), this);
+        }
+
+        String accessFailedMessage(Class<?> refc, Class<?> defc, int mods) {
+            // check the class first:
+            boolean classOK = (Modifier.isPublic(defc.getModifiers()) &&
+                    (defc == refc ||
+                            Modifier.isPublic(refc.getModifiers())));
+            if (!classOK && (allowedModes & PACKAGE) != 0) {
+                classOK = (VerifyAccess.isClassAccessible(defc, lookupClass(), ALL_MODES) &&
+                        (defc == refc ||
+                                VerifyAccess.isClassAccessible(refc, lookupClass(), ALL_MODES)));
+            }
+            if (!classOK)
+                return "class is not public";
+            if (Modifier.isPublic(mods))
+                return "access to public member failed";  // (how?)
+            if (Modifier.isPrivate(mods))
+                return "member is private";
+            if (Modifier.isProtected(mods))
+                return "member is protected";
+            return "member is private to package";
+        }
+
+        // Android-changed: checkSpecialCaller assumes that ALLOW_NESTMATE_ACCESS = false,
+        // as in upstream OpenJDK.
+        //
+        // private static final boolean ALLOW_NESTMATE_ACCESS = false;
+
+        private void checkSpecialCaller(Class<?> specialCaller) throws IllegalAccessException {
+            // Android-changed: No support for TRUSTED lookups. Also construct the
+            // IllegalAccessException by hand because the upstream code implicitly assumes
+            // that the lookupClass == specialCaller.
+            //
+            // if (allowedModes == TRUSTED)  return;
+            if (!hasPrivateAccess() || (specialCaller != lookupClass())) {
+                throw new IllegalAccessException("no private access for invokespecial : "
+                        + specialCaller + ", from" + this);
+            }
+        }
+
+        private void throwMakeAccessException(String message, Object from) throws
+                IllegalAccessException{
+            message = message + ": "+ toString();
+            if (from != null)  message += ", from " + from;
+            throw new IllegalAccessException(message);
+        }
+
+        private void checkReturnType(Method method, MethodType methodType)
+                throws NoSuchMethodException {
+            if (method.getReturnType() != methodType.rtype()) {
+                throw new NoSuchMethodException(method.getName() + methodType);
+            }
+        }
+    }
+
+    /**
+     * "Cracks" {@code target} to reveal the underlying {@code MethodHandleImpl}.
+     */
+    private static MethodHandleImpl getMethodHandleImpl(MethodHandle target) {
+        // Special case : We implement handles to constructors as transformers,
+        // so we must extract the underlying handle from the transformer.
+        if (target instanceof Transformers.Construct) {
+            target = ((Transformers.Construct) target).getConstructorHandle();
+        }
+
+        // Special case: Var-args methods are also implemented as Transformers,
+        // so we should get the underlying handle in that case as well.
+        if (target instanceof Transformers.VarargsCollector) {
+            target = target.asFixedArity();
+        }
+
+        if (target instanceof MethodHandleImpl) {
+            return (MethodHandleImpl) target;
+        }
+
+        throw new IllegalArgumentException(target + " is not a direct handle");
+    }
+
+    // BEGIN Android-added: method to check if a class is an array.
+    private static void checkClassIsArray(Class<?> c) {
+        if (!c.isArray()) {
+            throw new IllegalArgumentException("Not an array type: " + c);
+        }
+    }
+
+    private static void checkTypeIsViewable(Class<?> componentType) {
+        if (componentType == short.class ||
+            componentType == char.class ||
+            componentType == int.class ||
+            componentType == long.class ||
+            componentType == float.class ||
+            componentType == double.class) {
+            return;
+        }
+        throw new UnsupportedOperationException("Component type not supported: " + componentType);
+    }
+    // END Android-added: method to check if a class is an array.
+
+    /**
+     * Produces a method handle giving read access to elements of an array.
+     * The type of the method handle will have a return type of the array's
+     * element type.  Its first argument will be the array type,
+     * and the second will be {@code int}.
+     * @param arrayClass an array type
+     * @return a method handle which can load values from the given array type
+     * @throws NullPointerException if the argument is null
+     * @throws  IllegalArgumentException if arrayClass is not an array type
+     */
+    public static
+    MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException {
+        checkClassIsArray(arrayClass);
+        final Class<?> componentType = arrayClass.getComponentType();
+        if (componentType.isPrimitive()) {
+            try {
+                return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class,
+                        "arrayElementGetter",
+                        MethodType.methodType(componentType, arrayClass, int.class));
+            } catch (NoSuchMethodException | IllegalAccessException exception) {
+                throw new AssertionError(exception);
+            }
+        }
+
+        return new Transformers.ReferenceArrayElementGetter(arrayClass);
+    }
+
+    /** @hide */ public static byte arrayElementGetter(byte[] array, int i) { return array[i]; }
+    /** @hide */ public static boolean arrayElementGetter(boolean[] array, int i) { return array[i]; }
+    /** @hide */ public static char arrayElementGetter(char[] array, int i) { return array[i]; }
+    /** @hide */ public static short arrayElementGetter(short[] array, int i) { return array[i]; }
+    /** @hide */ public static int arrayElementGetter(int[] array, int i) { return array[i]; }
+    /** @hide */ public static long arrayElementGetter(long[] array, int i) { return array[i]; }
+    /** @hide */ public static float arrayElementGetter(float[] array, int i) { return array[i]; }
+    /** @hide */ public static double arrayElementGetter(double[] array, int i) { return array[i]; }
+
+    /**
+     * Produces a method handle giving write access to elements of an array.
+     * The type of the method handle will have a void return type.
+     * Its last argument will be the array's element type.
+     * The first and second arguments will be the array type and int.
+     * @param arrayClass the class of an array
+     * @return a method handle which can store values into the array type
+     * @throws NullPointerException if the argument is null
+     * @throws IllegalArgumentException if arrayClass is not an array type
+     */
+    public static
+    MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException {
+        checkClassIsArray(arrayClass);
+        final Class<?> componentType = arrayClass.getComponentType();
+        if (componentType.isPrimitive()) {
+            try {
+                return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class,
+                        "arrayElementSetter",
+                        MethodType.methodType(void.class, arrayClass, int.class, componentType));
+            } catch (NoSuchMethodException | IllegalAccessException exception) {
+                throw new AssertionError(exception);
+            }
+        }
+
+        return new Transformers.ReferenceArrayElementSetter(arrayClass);
+    }
+
+    /** @hide */
+    public static void arrayElementSetter(byte[] array, int i, byte val) { array[i] = val; }
+    /** @hide */
+    public static void arrayElementSetter(boolean[] array, int i, boolean val) { array[i] = val; }
+    /** @hide */
+    public static void arrayElementSetter(char[] array, int i, char val) { array[i] = val; }
+    /** @hide */
+    public static void arrayElementSetter(short[] array, int i, short val) { array[i] = val; }
+    /** @hide */
+    public static void arrayElementSetter(int[] array, int i, int val) { array[i] = val; }
+    /** @hide */
+    public static void arrayElementSetter(long[] array, int i, long val) { array[i] = val; }
+    /** @hide */
+    public static void arrayElementSetter(float[] array, int i, float val) { array[i] = val; }
+    /** @hide */
+    public static void arrayElementSetter(double[] array, int i, double val) { array[i] = val; }
+
+    // BEGIN Android-changed: OpenJDK 9+181 VarHandle API factory methods.
+    /**
+     * Produces a VarHandle giving access to elements of an array of type
+     * {@code arrayClass}.  The VarHandle's variable type is the component type
+     * of {@code arrayClass} and the list of coordinate types is
+     * {@code (arrayClass, int)}, where the {@code int} coordinate type
+     * corresponds to an argument that is an index into an array.
+     * <p>
+     * Certain access modes of the returned VarHandle are unsupported under
+     * the following conditions:
+     * <ul>
+     * <li>if the component type is anything other than {@code byte},
+     *     {@code short}, {@code char}, {@code int}, {@code long},
+     *     {@code float}, or {@code double} then numeric atomic update access
+     *     modes are unsupported.
+     * <li>if the field type is anything other than {@code boolean},
+     *     {@code byte}, {@code short}, {@code char}, {@code int} or
+     *     {@code long} then bitwise atomic update access modes are
+     *     unsupported.
+     * </ul>
+     * <p>
+     * If the component type is {@code float} or {@code double} then numeric
+     * and atomic update access modes compare values using their bitwise
+     * representation (see {@link Float#floatToRawIntBits} and
+     * {@link Double#doubleToRawLongBits}, respectively).
+     * @apiNote
+     * Bitwise comparison of {@code float} values or {@code double} values,
+     * as performed by the numeric and atomic update access modes, differ
+     * from the primitive {@code ==} operator and the {@link Float#equals}
+     * and {@link Double#equals} methods, specifically with respect to
+     * comparing NaN values or comparing {@code -0.0} with {@code +0.0}.
+     * Care should be taken when performing a compare and set or a compare
+     * and exchange operation with such values since the operation may
+     * unexpectedly fail.
+     * There are many possible NaN values that are considered to be
+     * {@code NaN} in Java, although no IEEE 754 floating-point operation
+     * provided by Java can distinguish between them.  Operation failure can
+     * occur if the expected or witness value is a NaN value and it is
+     * transformed (perhaps in a platform specific manner) into another NaN
+     * value, and thus has a different bitwise representation (see
+     * {@link Float#intBitsToFloat} or {@link Double#longBitsToDouble} for more
+     * details).
+     * The values {@code -0.0} and {@code +0.0} have different bitwise
+     * representations but are considered equal when using the primitive
+     * {@code ==} operator.  Operation failure can occur if, for example, a
+     * numeric algorithm computes an expected value to be say {@code -0.0}
+     * and previously computed the witness value to be say {@code +0.0}.
+     * @param arrayClass the class of an array, of type {@code T[]}
+     * @return a VarHandle giving access to elements of an array
+     * @throws NullPointerException if the arrayClass is null
+     * @throws IllegalArgumentException if arrayClass is not an array type
+     * @since 9
+     * @hide
+     */
+    public static
+    VarHandle arrayElementVarHandle(Class<?> arrayClass) throws IllegalArgumentException {
+        checkClassIsArray(arrayClass);
+        return ArrayElementVarHandle.create(arrayClass);
+    }
+
+    /**
+     * Produces a VarHandle giving access to elements of a {@code byte[]} array
+     * viewed as if it were a different primitive array type, such as
+     * {@code int[]} or {@code long[]}.
+     * The VarHandle's variable type is the component type of
+     * {@code viewArrayClass} and the list of coordinate types is
+     * {@code (byte[], int)}, where the {@code int} coordinate type
+     * corresponds to an argument that is an index into a {@code byte[]} array.
+     * The returned VarHandle accesses bytes at an index in a {@code byte[]}
+     * array, composing bytes to or from a value of the component type of
+     * {@code viewArrayClass} according to the given endianness.
+     * <p>
+     * The supported component types (variables types) are {@code short},
+     * {@code char}, {@code int}, {@code long}, {@code float} and
+     * {@code double}.
+     * <p>
+     * Access of bytes at a given index will result in an
+     * {@code IndexOutOfBoundsException} if the index is less than {@code 0}
+     * or greater than the {@code byte[]} array length minus the size (in bytes)
+     * of {@code T}.
+     * <p>
+     * Access of bytes at an index may be aligned or misaligned for {@code T},
+     * with respect to the underlying memory address, {@code A} say, associated
+     * with the array and index.
+     * If access is misaligned then access for anything other than the
+     * {@code get} and {@code set} access modes will result in an
+     * {@code IllegalStateException}.  In such cases atomic access is only
+     * guaranteed with respect to the largest power of two that divides the GCD
+     * of {@code A} and the size (in bytes) of {@code T}.
+     * If access is aligned then following access modes are supported and are
+     * guaranteed to support atomic access:
+     * <ul>
+     * <li>read write access modes for all {@code T}, with the exception of
+     *     access modes {@code get} and {@code set} for {@code long} and
+     *     {@code double} on 32-bit platforms.
+     * <li>atomic update access modes for {@code int}, {@code long},
+     *     {@code float} or {@code double}.
+     *     (Future major platform releases of the JDK may support additional
+     *     types for certain currently unsupported access modes.)
+     * <li>numeric atomic update access modes for {@code int} and {@code long}.
+     *     (Future major platform releases of the JDK may support additional
+     *     numeric types for certain currently unsupported access modes.)
+     * <li>bitwise atomic update access modes for {@code int} and {@code long}.
+     *     (Future major platform releases of the JDK may support additional
+     *     numeric types for certain currently unsupported access modes.)
+     * </ul>
+     * <p>
+     * Misaligned access, and therefore atomicity guarantees, may be determined
+     * for {@code byte[]} arrays without operating on a specific array.  Given
+     * an {@code index}, {@code T} and it's corresponding boxed type,
+     * {@code T_BOX}, misalignment may be determined as follows:
+     * <pre>{@code
+     * int sizeOfT = T_BOX.BYTES;  // size in bytes of T
+     * int misalignedAtZeroIndex = ByteBuffer.wrap(new byte[0]).
+     *     alignmentOffset(0, sizeOfT);
+     * int misalignedAtIndex = (misalignedAtZeroIndex + index) % sizeOfT;
+     * boolean isMisaligned = misalignedAtIndex != 0;
+     * }</pre>
+     * <p>
+     * If the variable type is {@code float} or {@code double} then atomic
+     * update access modes compare values using their bitwise representation
+     * (see {@link Float#floatToRawIntBits} and
+     * {@link Double#doubleToRawLongBits}, respectively).
+     * @param viewArrayClass the view array class, with a component type of
+     * type {@code T}
+     * @param byteOrder the endianness of the view array elements, as
+     * stored in the underlying {@code byte} array
+     * @return a VarHandle giving access to elements of a {@code byte[]} array
+     * viewed as if elements corresponding to the components type of the view
+     * array class
+     * @throws NullPointerException if viewArrayClass or byteOrder is null
+     * @throws IllegalArgumentException if viewArrayClass is not an array type
+     * @throws UnsupportedOperationException if the component type of
+     * viewArrayClass is not supported as a variable type
+     * @since 9
+     * @hide
+     */
+    public static
+    VarHandle byteArrayViewVarHandle(Class<?> viewArrayClass,
+                                     ByteOrder byteOrder) throws IllegalArgumentException {
+        checkClassIsArray(viewArrayClass);
+        checkTypeIsViewable(viewArrayClass.getComponentType());
+        return ByteArrayViewVarHandle.create(viewArrayClass, byteOrder);
+    }
+
+    /**
+     * Produces a VarHandle giving access to elements of a {@code ByteBuffer}
+     * viewed as if it were an array of elements of a different primitive
+     * component type to that of {@code byte}, such as {@code int[]} or
+     * {@code long[]}.
+     * The VarHandle's variable type is the component type of
+     * {@code viewArrayClass} and the list of coordinate types is
+     * {@code (ByteBuffer, int)}, where the {@code int} coordinate type
+     * corresponds to an argument that is an index into a {@code byte[]} array.
+     * The returned VarHandle accesses bytes at an index in a
+     * {@code ByteBuffer}, composing bytes to or from a value of the component
+     * type of {@code viewArrayClass} according to the given endianness.
+     * <p>
+     * The supported component types (variables types) are {@code short},
+     * {@code char}, {@code int}, {@code long}, {@code float} and
+     * {@code double}.
+     * <p>
+     * Access will result in a {@code ReadOnlyBufferException} for anything
+     * other than the read access modes if the {@code ByteBuffer} is read-only.
+     * <p>
+     * Access of bytes at a given index will result in an
+     * {@code IndexOutOfBoundsException} if the index is less than {@code 0}
+     * or greater than the {@code ByteBuffer} limit minus the size (in bytes) of
+     * {@code T}.
+     * <p>
+     * Access of bytes at an index may be aligned or misaligned for {@code T},
+     * with respect to the underlying memory address, {@code A} say, associated
+     * with the {@code ByteBuffer} and index.
+     * If access is misaligned then access for anything other than the
+     * {@code get} and {@code set} access modes will result in an
+     * {@code IllegalStateException}.  In such cases atomic access is only
+     * guaranteed with respect to the largest power of two that divides the GCD
+     * of {@code A} and the size (in bytes) of {@code T}.
+     * If access is aligned then following access modes are supported and are
+     * guaranteed to support atomic access:
+     * <ul>
+     * <li>read write access modes for all {@code T}, with the exception of
+     *     access modes {@code get} and {@code set} for {@code long} and
+     *     {@code double} on 32-bit platforms.
+     * <li>atomic update access modes for {@code int}, {@code long},
+     *     {@code float} or {@code double}.
+     *     (Future major platform releases of the JDK may support additional
+     *     types for certain currently unsupported access modes.)
+     * <li>numeric atomic update access modes for {@code int} and {@code long}.
+     *     (Future major platform releases of the JDK may support additional
+     *     numeric types for certain currently unsupported access modes.)
+     * <li>bitwise atomic update access modes for {@code int} and {@code long}.
+     *     (Future major platform releases of the JDK may support additional
+     *     numeric types for certain currently unsupported access modes.)
+     * </ul>
+     * <p>
+     * Misaligned access, and therefore atomicity guarantees, may be determined
+     * for a {@code ByteBuffer}, {@code bb} (direct or otherwise), an
+     * {@code index}, {@code T} and it's corresponding boxed type,
+     * {@code T_BOX}, as follows:
+     * <pre>{@code
+     * int sizeOfT = T_BOX.BYTES;  // size in bytes of T
+     * ByteBuffer bb = ...
+     * int misalignedAtIndex = bb.alignmentOffset(index, sizeOfT);
+     * boolean isMisaligned = misalignedAtIndex != 0;
+     * }</pre>
+     * <p>
+     * If the variable type is {@code float} or {@code double} then atomic
+     * update access modes compare values using their bitwise representation
+     * (see {@link Float#floatToRawIntBits} and
+     * {@link Double#doubleToRawLongBits}, respectively).
+     * @param viewArrayClass the view array class, with a component type of
+     * type {@code T}
+     * @param byteOrder the endianness of the view array elements, as
+     * stored in the underlying {@code ByteBuffer} (Note this overrides the
+     * endianness of a {@code ByteBuffer})
+     * @return a VarHandle giving access to elements of a {@code ByteBuffer}
+     * viewed as if elements corresponding to the components type of the view
+     * array class
+     * @throws NullPointerException if viewArrayClass or byteOrder is null
+     * @throws IllegalArgumentException if viewArrayClass is not an array type
+     * @throws UnsupportedOperationException if the component type of
+     * viewArrayClass is not supported as a variable type
+     * @since 9
+     * @hide
+     */
+    public static
+    VarHandle byteBufferViewVarHandle(Class<?> viewArrayClass,
+                                      ByteOrder byteOrder) throws IllegalArgumentException {
+        checkClassIsArray(viewArrayClass);
+        checkTypeIsViewable(viewArrayClass.getComponentType());
+        return ByteBufferViewVarHandle.create(viewArrayClass, byteOrder);
+    }
+    // END Android-changed: OpenJDK 9+181 VarHandle API factory methods.
+
+    /// method handle invocation (reflective style)
+
+    /**
+     * Produces a method handle which will invoke any method handle of the
+     * given {@code type}, with a given number of trailing arguments replaced by
+     * a single trailing {@code Object[]} array.
+     * The resulting invoker will be a method handle with the following
+     * arguments:
+     * <ul>
+     * <li>a single {@code MethodHandle} target
+     * <li>zero or more leading values (counted by {@code leadingArgCount})
+     * <li>an {@code Object[]} array containing trailing arguments
+     * </ul>
+     * <p>
+     * The invoker will invoke its target like a call to {@link MethodHandle#invoke invoke} with
+     * the indicated {@code type}.
+     * That is, if the target is exactly of the given {@code type}, it will behave
+     * like {@code invokeExact}; otherwise it behave as if {@link MethodHandle#asType asType}
+     * is used to convert the target to the required {@code type}.
+     * <p>
+     * The type of the returned invoker will not be the given {@code type}, but rather
+     * will have all parameters except the first {@code leadingArgCount}
+     * replaced by a single array of type {@code Object[]}, which will be
+     * the final parameter.
+     * <p>
+     * Before invoking its target, the invoker will spread the final array, apply
+     * reference casts as necessary, and unbox and widen primitive arguments.
+     * If, when the invoker is called, the supplied array argument does
+     * not have the correct number of elements, the invoker will throw
+     * an {@link IllegalArgumentException} instead of invoking the target.
+     * <p>
+     * This method is equivalent to the following code (though it may be more efficient):
+     * <blockquote><pre>{@code
+MethodHandle invoker = MethodHandles.invoker(type);
+int spreadArgCount = type.parameterCount() - leadingArgCount;
+invoker = invoker.asSpreader(Object[].class, spreadArgCount);
+return invoker;
+     * }</pre></blockquote>
+     * This method throws no reflective or security exceptions.
+     * @param type the desired target type
+     * @param leadingArgCount number of fixed arguments, to be passed unchanged to the target
+     * @return a method handle suitable for invoking any method handle of the given type
+     * @throws NullPointerException if {@code type} is null
+     * @throws IllegalArgumentException if {@code leadingArgCount} is not in
+     *                  the range from 0 to {@code type.parameterCount()} inclusive,
+     *                  or if the resulting method handle's type would have
+     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
+     */
     static public
-    MethodHandle spreadInvoker(MethodType type, int leadingArgCount) { return null; }
+    MethodHandle spreadInvoker(MethodType type, int leadingArgCount) {
+        if (leadingArgCount < 0 || leadingArgCount > type.parameterCount())
+            throw newIllegalArgumentException("bad argument count", leadingArgCount);
 
+        MethodHandle invoker = MethodHandles.invoker(type);
+        int spreadArgCount = type.parameterCount() - leadingArgCount;
+        invoker = invoker.asSpreader(Object[].class, spreadArgCount);
+        return invoker;
+    }
+
+    /**
+     * Produces a special <em>invoker method handle</em> which can be used to
+     * invoke any method handle of the given type, as if by {@link MethodHandle#invokeExact invokeExact}.
+     * The resulting invoker will have a type which is
+     * exactly equal to the desired type, except that it will accept
+     * an additional leading argument of type {@code MethodHandle}.
+     * <p>
+     * This method is equivalent to the following code (though it may be more efficient):
+     * {@code publicLookup().findVirtual(MethodHandle.class, "invokeExact", type)}
+     *
+     * <p style="font-size:smaller;">
+     * <em>Discussion:</em>
+     * Invoker method handles can be useful when working with variable method handles
+     * of unknown types.
+     * For example, to emulate an {@code invokeExact} call to a variable method
+     * handle {@code M}, extract its type {@code T},
+     * look up the invoker method {@code X} for {@code T},
+     * and call the invoker method, as {@code X.invoke(T, A...)}.
+     * (It would not work to call {@code X.invokeExact}, since the type {@code T}
+     * is unknown.)
+     * If spreading, collecting, or other argument transformations are required,
+     * they can be applied once to the invoker {@code X} and reused on many {@code M}
+     * method handle values, as long as they are compatible with the type of {@code X}.
+     * <p style="font-size:smaller;">
+     * <em>(Note:  The invoker method is not available via the Core Reflection API.
+     * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
+     * on the declared {@code invokeExact} or {@code invoke} method will raise an
+     * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em>
+     * <p>
+     * This method throws no reflective or security exceptions.
+     * @param type the desired target type
+     * @return a method handle suitable for invoking any method handle of the given type
+     * @throws IllegalArgumentException if the resulting method handle's type would have
+     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
+     */
     static public
-    MethodHandle exactInvoker(MethodType type) { return null; }
+    MethodHandle exactInvoker(MethodType type) {
+        return new Transformers.Invoker(type, true /* isExactInvoker */);
+    }
 
+    /**
+     * Produces a special <em>invoker method handle</em> which can be used to
+     * invoke any method handle compatible with the given type, as if by {@link MethodHandle#invoke invoke}.
+     * The resulting invoker will have a type which is
+     * exactly equal to the desired type, except that it will accept
+     * an additional leading argument of type {@code MethodHandle}.
+     * <p>
+     * Before invoking its target, if the target differs from the expected type,
+     * the invoker will apply reference casts as
+     * necessary and box, unbox, or widen primitive values, as if by {@link MethodHandle#asType asType}.
+     * Similarly, the return value will be converted as necessary.
+     * If the target is a {@linkplain MethodHandle#asVarargsCollector variable arity method handle},
+     * the required arity conversion will be made, again as if by {@link MethodHandle#asType asType}.
+     * <p>
+     * This method is equivalent to the following code (though it may be more efficient):
+     * {@code publicLookup().findVirtual(MethodHandle.class, "invoke", type)}
+     * <p style="font-size:smaller;">
+     * <em>Discussion:</em>
+     * A {@linkplain MethodType#genericMethodType general method type} is one which
+     * mentions only {@code Object} arguments and return values.
+     * An invoker for such a type is capable of calling any method handle
+     * of the same arity as the general type.
+     * <p style="font-size:smaller;">
+     * <em>(Note:  The invoker method is not available via the Core Reflection API.
+     * An attempt to call {@linkplain java.lang.reflect.Method#invoke java.lang.reflect.Method.invoke}
+     * on the declared {@code invokeExact} or {@code invoke} method will raise an
+     * {@link java.lang.UnsupportedOperationException UnsupportedOperationException}.)</em>
+     * <p>
+     * This method throws no reflective or security exceptions.
+     * @param type the desired target type
+     * @return a method handle suitable for invoking any method handle convertible to the given type
+     * @throws IllegalArgumentException if the resulting method handle's type would have
+     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
+     */
     static public
-    MethodHandle invoker(MethodType type) { return null; }
+    MethodHandle invoker(MethodType type) {
+        return new Transformers.Invoker(type, false /* isExactInvoker */);
+    }
 
+    /**
+     * Produces a special <em>invoker method handle</em> which can be used to
+     * invoke a signature-polymorphic access mode method on any VarHandle whose
+     * associated access mode type is compatible with the given type.
+     * The resulting invoker will have a type which is exactly equal to the
+     * desired given type, except that it will accept an additional leading
+     * argument of type {@code VarHandle}.
+     *
+     * @param accessMode the VarHandle access mode
+     * @param type the desired target type
+     * @return a method handle suitable for invoking an access mode method of
+     *         any VarHandle whose access mode type is of the given type.
+     * @since 9
+     * @hide
+     */
+    static public
+    MethodHandle varHandleExactInvoker(VarHandle.AccessMode accessMode, MethodType type) {
+        unsupported("MethodHandles.varHandleExactInvoker()");  // TODO(b/65872996)
+        return null;
+    }
+
+    /**
+     * Produces a special <em>invoker method handle</em> which can be used to
+     * invoke a signature-polymorphic access mode method on any VarHandle whose
+     * associated access mode type is compatible with the given type.
+     * The resulting invoker will have a type which is exactly equal to the
+     * desired given type, except that it will accept an additional leading
+     * argument of type {@code VarHandle}.
+     * <p>
+     * Before invoking its target, if the access mode type differs from the
+     * desired given type, the invoker will apply reference casts as necessary
+     * and box, unbox, or widen primitive values, as if by
+     * {@link MethodHandle#asType asType}.  Similarly, the return value will be
+     * converted as necessary.
+     * <p>
+     * This method is equivalent to the following code (though it may be more
+     * efficient): {@code publicLookup().findVirtual(VarHandle.class, accessMode.name(), type)}
+     *
+     * @param accessMode the VarHandle access mode
+     * @param type the desired target type
+     * @return a method handle suitable for invoking an access mode method of
+     *         any VarHandle whose access mode type is convertible to the given
+     *         type.
+     * @since 9
+     * @hide
+     */
+    static public
+    MethodHandle varHandleInvoker(VarHandle.AccessMode accessMode, MethodType type) {
+        unsupported("MethodHandles.varHandleInvoker()");  // TODO(b/65872996)
+        return null;
+    }
+
+    // Android-changed: Basic invokers are not supported.
+    //
+    // static /*non-public*/
+    // MethodHandle basicInvoker(MethodType type) {
+    //     return type.invokers().basicInvoker();
+    // }
+
+     /// method handle modification (creation from other method handles)
+
+    /**
+     * Produces a method handle which adapts the type of the
+     * given method handle to a new type by pairwise argument and return type conversion.
+     * The original type and new type must have the same number of arguments.
+     * The resulting method handle is guaranteed to report a type
+     * which is equal to the desired new type.
+     * <p>
+     * If the original type and new type are equal, returns target.
+     * <p>
+     * The same conversions are allowed as for {@link MethodHandle#asType MethodHandle.asType},
+     * and some additional conversions are also applied if those conversions fail.
+     * Given types <em>T0</em>, <em>T1</em>, one of the following conversions is applied
+     * if possible, before or instead of any conversions done by {@code asType}:
+     * <ul>
+     * <li>If <em>T0</em> and <em>T1</em> are references, and <em>T1</em> is an interface type,
+     *     then the value of type <em>T0</em> is passed as a <em>T1</em> without a cast.
+     *     (This treatment of interfaces follows the usage of the bytecode verifier.)
+     * <li>If <em>T0</em> is boolean and <em>T1</em> is another primitive,
+     *     the boolean is converted to a byte value, 1 for true, 0 for false.
+     *     (This treatment follows the usage of the bytecode verifier.)
+     * <li>If <em>T1</em> is boolean and <em>T0</em> is another primitive,
+     *     <em>T0</em> is converted to byte via Java casting conversion (JLS 5.5),
+     *     and the low order bit of the result is tested, as if by {@code (x & 1) != 0}.
+     * <li>If <em>T0</em> and <em>T1</em> are primitives other than boolean,
+     *     then a Java casting conversion (JLS 5.5) is applied.
+     *     (Specifically, <em>T0</em> will convert to <em>T1</em> by
+     *     widening and/or narrowing.)
+     * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive, an unboxing
+     *     conversion will be applied at runtime, possibly followed
+     *     by a Java casting conversion (JLS 5.5) on the primitive value,
+     *     possibly followed by a conversion from byte to boolean by testing
+     *     the low-order bit.
+     * <li>If <em>T0</em> is a reference and <em>T1</em> a primitive,
+     *     and if the reference is null at runtime, a zero value is introduced.
+     * </ul>
+     * @param target the method handle to invoke after arguments are retyped
+     * @param newType the expected type of the new method handle
+     * @return a method handle which delegates to the target after performing
+     *           any necessary argument conversions, and arranges for any
+     *           necessary return value conversions
+     * @throws NullPointerException if either argument is null
+     * @throws WrongMethodTypeException if the conversion cannot be made
+     * @see MethodHandle#asType
+     */
     public static
-    MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) { return null; }
+    MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) {
+        explicitCastArgumentsChecks(target, newType);
+        // use the asTypeCache when possible:
+        MethodType oldType = target.type();
+        if (oldType == newType) return target;
+        if (oldType.explicitCastEquivalentToAsType(newType)) {
+            return target.asFixedArity().asType(newType);
+        }
 
+        return new Transformers.ExplicitCastArguments(target, newType);
+    }
+
+    private static void explicitCastArgumentsChecks(MethodHandle target, MethodType newType) {
+        if (target.type().parameterCount() != newType.parameterCount()) {
+            throw new WrongMethodTypeException("cannot explicitly cast " + target + " to " + newType);
+        }
+    }
+
+    /**
+     * Produces a method handle which adapts the calling sequence of the
+     * given method handle to a new type, by reordering the arguments.
+     * The resulting method handle is guaranteed to report a type
+     * which is equal to the desired new type.
+     * <p>
+     * The given array controls the reordering.
+     * Call {@code #I} the number of incoming parameters (the value
+     * {@code newType.parameterCount()}, and call {@code #O} the number
+     * of outgoing parameters (the value {@code target.type().parameterCount()}).
+     * Then the length of the reordering array must be {@code #O},
+     * and each element must be a non-negative number less than {@code #I}.
+     * For every {@code N} less than {@code #O}, the {@code N}-th
+     * outgoing argument will be taken from the {@code I}-th incoming
+     * argument, where {@code I} is {@code reorder[N]}.
+     * <p>
+     * No argument or return value conversions are applied.
+     * The type of each incoming argument, as determined by {@code newType},
+     * must be identical to the type of the corresponding outgoing parameter
+     * or parameters in the target method handle.
+     * The return type of {@code newType} must be identical to the return
+     * type of the original target.
+     * <p>
+     * The reordering array need not specify an actual permutation.
+     * An incoming argument will be duplicated if its index appears
+     * more than once in the array, and an incoming argument will be dropped
+     * if its index does not appear in the array.
+     * As in the case of {@link #dropArguments(MethodHandle,int,List) dropArguments},
+     * incoming arguments which are not mentioned in the reordering array
+     * are may be any type, as determined only by {@code newType}.
+     * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodType intfn1 = methodType(int.class, int.class);
+MethodType intfn2 = methodType(int.class, int.class, int.class);
+MethodHandle sub = ... (int x, int y) -> (x-y) ...;
+assert(sub.type().equals(intfn2));
+MethodHandle sub1 = permuteArguments(sub, intfn2, 0, 1);
+MethodHandle rsub = permuteArguments(sub, intfn2, 1, 0);
+assert((int)rsub.invokeExact(1, 100) == 99);
+MethodHandle add = ... (int x, int y) -> (x+y) ...;
+assert(add.type().equals(intfn2));
+MethodHandle twice = permuteArguments(add, intfn1, 0, 0);
+assert(twice.type().equals(intfn1));
+assert((int)twice.invokeExact(21) == 42);
+     * }</pre></blockquote>
+     * @param target the method handle to invoke after arguments are reordered
+     * @param newType the expected type of the new method handle
+     * @param reorder an index array which controls the reordering
+     * @return a method handle which delegates to the target after it
+     *           drops unused arguments and moves and/or duplicates the other arguments
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if the index array length is not equal to
+     *                  the arity of the target, or if any index array element
+     *                  not a valid index for a parameter of {@code newType},
+     *                  or if two corresponding parameter types in
+     *                  {@code target.type()} and {@code newType} are not identical,
+     */
     public static
-    MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) { return null; }
+    MethodHandle permuteArguments(MethodHandle target, MethodType newType, int... reorder) {
+        reorder = reorder.clone();  // get a private copy
+        MethodType oldType = target.type();
+        permuteArgumentChecks(reorder, newType, oldType);
 
+        return new Transformers.PermuteArguments(newType, target, reorder);
+    }
+
+    // Android-changed: findFirstDupOrDrop is unused and removed.
+    // private static int findFirstDupOrDrop(int[] reorder, int newArity);
+
+    private static boolean permuteArgumentChecks(int[] reorder, MethodType newType, MethodType oldType) {
+        if (newType.returnType() != oldType.returnType())
+            throw newIllegalArgumentException("return types do not match",
+                    oldType, newType);
+        if (reorder.length == oldType.parameterCount()) {
+            int limit = newType.parameterCount();
+            boolean bad = false;
+            for (int j = 0; j < reorder.length; j++) {
+                int i = reorder[j];
+                if (i < 0 || i >= limit) {
+                    bad = true; break;
+                }
+                Class<?> src = newType.parameterType(i);
+                Class<?> dst = oldType.parameterType(j);
+                if (src != dst)
+                    throw newIllegalArgumentException("parameter types do not match after reorder",
+                            oldType, newType);
+            }
+            if (!bad)  return true;
+        }
+        throw newIllegalArgumentException("bad reorder array: "+Arrays.toString(reorder));
+    }
+
+    /**
+     * Produces a method handle of the requested return type which returns the given
+     * constant value every time it is invoked.
+     * <p>
+     * Before the method handle is returned, the passed-in value is converted to the requested type.
+     * If the requested type is primitive, widening primitive conversions are attempted,
+     * else reference conversions are attempted.
+     * <p>The returned method handle is equivalent to {@code identity(type).bindTo(value)}.
+     * @param type the return type of the desired method handle
+     * @param value the value to return
+     * @return a method handle of the given return type and no arguments, which always returns the given value
+     * @throws NullPointerException if the {@code type} argument is null
+     * @throws ClassCastException if the value cannot be converted to the required return type
+     * @throws IllegalArgumentException if the given type is {@code void.class}
+     */
     public static
-    MethodHandle constant(Class<?> type, Object value) { return null; }
+    MethodHandle constant(Class<?> type, Object value) {
+        if (type.isPrimitive()) {
+            if (type == void.class)
+                throw newIllegalArgumentException("void type");
+            Wrapper w = Wrapper.forPrimitiveType(type);
+            value = w.convert(value, type);
+        }
 
+        return new Transformers.Constant(type, value);
+    }
+
+    /**
+     * Produces a method handle which returns its sole argument when invoked.
+     * @param type the type of the sole parameter and return value of the desired method handle
+     * @return a unary method handle which accepts and returns the given type
+     * @throws NullPointerException if the argument is null
+     * @throws IllegalArgumentException if the given type is {@code void.class}
+     */
     public static
-    MethodHandle identity(Class<?> type) { return null; }
+    MethodHandle identity(Class<?> type) {
+        if (type == null) {
+            throw new NullPointerException("type == null");
+        }
 
+        if (type.isPrimitive()) {
+            try {
+                return Lookup.PUBLIC_LOOKUP.findStatic(MethodHandles.class, "identity",
+                        MethodType.methodType(type, type));
+            } catch (NoSuchMethodException | IllegalAccessException e) {
+                throw new AssertionError(e);
+            }
+        }
+
+        return new Transformers.ReferenceIdentity(type);
+    }
+
+    /** @hide */ public static byte identity(byte val) { return val; }
+    /** @hide */ public static boolean identity(boolean val) { return val; }
+    /** @hide */ public static char identity(char val) { return val; }
+    /** @hide */ public static short identity(short val) { return val; }
+    /** @hide */ public static int identity(int val) { return val; }
+    /** @hide */ public static long identity(long val) { return val; }
+    /** @hide */ public static float identity(float val) { return val; }
+    /** @hide */ public static double identity(double val) { return val; }
+
+    /**
+     * Provides a target method handle with one or more <em>bound arguments</em>
+     * in advance of the method handle's invocation.
+     * The formal parameters to the target corresponding to the bound
+     * arguments are called <em>bound parameters</em>.
+     * Returns a new method handle which saves away the bound arguments.
+     * When it is invoked, it receives arguments for any non-bound parameters,
+     * binds the saved arguments to their corresponding parameters,
+     * and calls the original target.
+     * <p>
+     * The type of the new method handle will drop the types for the bound
+     * parameters from the original target type, since the new method handle
+     * will no longer require those arguments to be supplied by its callers.
+     * <p>
+     * Each given argument object must match the corresponding bound parameter type.
+     * If a bound parameter type is a primitive, the argument object
+     * must be a wrapper, and will be unboxed to produce the primitive value.
+     * <p>
+     * The {@code pos} argument selects which parameters are to be bound.
+     * It may range between zero and <i>N-L</i> (inclusively),
+     * where <i>N</i> is the arity of the target method handle
+     * and <i>L</i> is the length of the values array.
+     * @param target the method handle to invoke after the argument is inserted
+     * @param pos where to insert the argument (zero for the first)
+     * @param values the series of arguments to insert
+     * @return a method handle which inserts an additional argument,
+     *         before calling the original method handle
+     * @throws NullPointerException if the target or the {@code values} array is null
+     * @see MethodHandle#bindTo
+     */
     public static
-    MethodHandle insertArguments(MethodHandle target, int pos, Object... values) { return null; }
+    MethodHandle insertArguments(MethodHandle target, int pos, Object... values) {
+        int insCount = values.length;
+        Class<?>[] ptypes = insertArgumentsChecks(target, insCount, pos);
+        if (insCount == 0)  {
+            return target;
+        }
 
+        // Throw ClassCastExceptions early if we can't cast any of the provided values
+        // to the required type.
+        for (int i = 0; i < insCount; i++) {
+            final Class<?> ptype = ptypes[pos + i];
+            if (!ptype.isPrimitive()) {
+                ptypes[pos + i].cast(values[i]);
+            } else {
+                // Will throw a ClassCastException if something terrible happens.
+                values[i] = Wrapper.forPrimitiveType(ptype).convert(values[i], ptype);
+            }
+        }
+
+        return new Transformers.InsertArguments(target, pos, values);
+    }
+
+    // Android-changed: insertArgumentPrimitive is unused.
+    //
+    // private static BoundMethodHandle insertArgumentPrimitive(BoundMethodHandle result, int pos,
+    //                                                          Class<?> ptype, Object value) {
+    //     Wrapper w = Wrapper.forPrimitiveType(ptype);
+    //     // perform unboxing and/or primitive conversion
+    //     value = w.convert(value, ptype);
+    //     switch (w) {
+    //     case INT:     return result.bindArgumentI(pos, (int)value);
+    //     case LONG:    return result.bindArgumentJ(pos, (long)value);
+    //     case FLOAT:   return result.bindArgumentF(pos, (float)value);
+    //     case DOUBLE:  return result.bindArgumentD(pos, (double)value);
+    //     default:      return result.bindArgumentI(pos, ValueConversions.widenSubword(value));
+    //     }
+    // }
+
+    private static Class<?>[] insertArgumentsChecks(MethodHandle target, int insCount, int pos) throws RuntimeException {
+        MethodType oldType = target.type();
+        int outargs = oldType.parameterCount();
+        int inargs  = outargs - insCount;
+        if (inargs < 0)
+            throw newIllegalArgumentException("too many values to insert");
+        if (pos < 0 || pos > inargs)
+            throw newIllegalArgumentException("no argument type to append");
+        return oldType.ptypes();
+    }
+
+    /**
+     * Produces a method handle which will discard some dummy arguments
+     * before calling some other specified <i>target</i> method handle.
+     * The type of the new method handle will be the same as the target's type,
+     * except it will also include the dummy argument types,
+     * at some given position.
+     * <p>
+     * The {@code pos} argument may range between zero and <i>N</i>,
+     * where <i>N</i> is the arity of the target.
+     * If {@code pos} is zero, the dummy arguments will precede
+     * the target's real arguments; if {@code pos} is <i>N</i>
+     * they will come after.
+     * <p>
+     * <b>Example:</b>
+     * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodType bigType = cat.type().insertParameterTypes(0, int.class, String.class);
+MethodHandle d0 = dropArguments(cat, 0, bigType.parameterList().subList(0,2));
+assertEquals(bigType, d0.type());
+assertEquals("yz", (String) d0.invokeExact(123, "x", "y", "z"));
+     * }</pre></blockquote>
+     * <p>
+     * This method is also equivalent to the following code:
+     * <blockquote><pre>
+     * {@link #dropArguments(MethodHandle,int,Class...) dropArguments}{@code (target, pos, valueTypes.toArray(new Class[0]))}
+     * </pre></blockquote>
+     * @param target the method handle to invoke after the arguments are dropped
+     * @param valueTypes the type(s) of the argument(s) to drop
+     * @param pos position of first argument to drop (zero for the leftmost)
+     * @return a method handle which drops arguments of the given types,
+     *         before calling the original method handle
+     * @throws NullPointerException if the target is null,
+     *                              or if the {@code valueTypes} list or any of its elements is null
+     * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class},
+     *                  or if {@code pos} is negative or greater than the arity of the target,
+     *                  or if the new method handle's type would have too many parameters
+     */
     public static
-    MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) { return null; }
+    MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) {
+        valueTypes = copyTypes(valueTypes);
+        MethodType oldType = target.type();  // get NPE
+        int dropped = dropArgumentChecks(oldType, pos, valueTypes);
 
+        MethodType newType = oldType.insertParameterTypes(pos, valueTypes);
+        if (dropped == 0) {
+            return target;
+        }
+
+        return new Transformers.DropArguments(newType, target, pos, valueTypes.size());
+    }
+
+    private static List<Class<?>> copyTypes(List<Class<?>> types) {
+        Object[] a = types.toArray();
+        return Arrays.asList(Arrays.copyOf(a, a.length, Class[].class));
+    }
+
+    private static int dropArgumentChecks(MethodType oldType, int pos, List<Class<?>> valueTypes) {
+        int dropped = valueTypes.size();
+        MethodType.checkSlotCount(dropped);
+        int outargs = oldType.parameterCount();
+        int inargs  = outargs + dropped;
+        if (pos < 0 || pos > outargs)
+            throw newIllegalArgumentException("no argument type to remove"
+                    + Arrays.asList(oldType, pos, valueTypes, inargs, outargs)
+                    );
+        return dropped;
+    }
+
+    /**
+     * Produces a method handle which will discard some dummy arguments
+     * before calling some other specified <i>target</i> method handle.
+     * The type of the new method handle will be the same as the target's type,
+     * except it will also include the dummy argument types,
+     * at some given position.
+     * <p>
+     * The {@code pos} argument may range between zero and <i>N</i>,
+     * where <i>N</i> is the arity of the target.
+     * If {@code pos} is zero, the dummy arguments will precede
+     * the target's real arguments; if {@code pos} is <i>N</i>
+     * they will come after.
+     * <p>
+     * <b>Example:</b>
+     * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodHandle d0 = dropArguments(cat, 0, String.class);
+assertEquals("yz", (String) d0.invokeExact("x", "y", "z"));
+MethodHandle d1 = dropArguments(cat, 1, String.class);
+assertEquals("xz", (String) d1.invokeExact("x", "y", "z"));
+MethodHandle d2 = dropArguments(cat, 2, String.class);
+assertEquals("xy", (String) d2.invokeExact("x", "y", "z"));
+MethodHandle d12 = dropArguments(cat, 1, int.class, boolean.class);
+assertEquals("xz", (String) d12.invokeExact("x", 12, true, "z"));
+     * }</pre></blockquote>
+     * <p>
+     * This method is also equivalent to the following code:
+     * <blockquote><pre>
+     * {@link #dropArguments(MethodHandle,int,List) dropArguments}{@code (target, pos, Arrays.asList(valueTypes))}
+     * </pre></blockquote>
+     * @param target the method handle to invoke after the arguments are dropped
+     * @param valueTypes the type(s) of the argument(s) to drop
+     * @param pos position of first argument to drop (zero for the leftmost)
+     * @return a method handle which drops arguments of the given types,
+     *         before calling the original method handle
+     * @throws NullPointerException if the target is null,
+     *                              or if the {@code valueTypes} array or any of its elements is null
+     * @throws IllegalArgumentException if any element of {@code valueTypes} is {@code void.class},
+     *                  or if {@code pos} is negative or greater than the arity of the target,
+     *                  or if the new method handle's type would have
+     *                  <a href="MethodHandle.html#maxarity">too many parameters</a>
+     */
     public static
-    MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) { return null; }
+    MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) {
+        return dropArguments(target, pos, Arrays.asList(valueTypes));
+    }
 
+    /**
+     * Adapts a target method handle by pre-processing
+     * one or more of its arguments, each with its own unary filter function,
+     * and then calling the target with each pre-processed argument
+     * replaced by the result of its corresponding filter function.
+     * <p>
+     * The pre-processing is performed by one or more method handles,
+     * specified in the elements of the {@code filters} array.
+     * The first element of the filter array corresponds to the {@code pos}
+     * argument of the target, and so on in sequence.
+     * <p>
+     * Null arguments in the array are treated as identity functions,
+     * and the corresponding arguments left unchanged.
+     * (If there are no non-null elements in the array, the original target is returned.)
+     * Each filter is applied to the corresponding argument of the adapter.
+     * <p>
+     * If a filter {@code F} applies to the {@code N}th argument of
+     * the target, then {@code F} must be a method handle which
+     * takes exactly one argument.  The type of {@code F}'s sole argument
+     * replaces the corresponding argument type of the target
+     * in the resulting adapted method handle.
+     * The return type of {@code F} must be identical to the corresponding
+     * parameter type of the target.
+     * <p>
+     * It is an error if there are elements of {@code filters}
+     * (null or not)
+     * which do not correspond to argument positions in the target.
+     * <p><b>Example:</b>
+     * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+MethodHandle upcase = lookup().findVirtual(String.class,
+  "toUpperCase", methodType(String.class));
+assertEquals("xy", (String) cat.invokeExact("x", "y"));
+MethodHandle f0 = filterArguments(cat, 0, upcase);
+assertEquals("Xy", (String) f0.invokeExact("x", "y")); // Xy
+MethodHandle f1 = filterArguments(cat, 1, upcase);
+assertEquals("xY", (String) f1.invokeExact("x", "y")); // xY
+MethodHandle f2 = filterArguments(cat, 0, upcase, upcase);
+assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY
+     * }</pre></blockquote>
+     * <p> Here is pseudocode for the resulting adapter:
+     * <blockquote><pre>{@code
+     * V target(P... p, A[i]... a[i], B... b);
+     * A[i] filter[i](V[i]);
+     * T adapter(P... p, V[i]... v[i], B... b) {
+     *   return target(p..., f[i](v[i])..., b...);
+     * }
+     * }</pre></blockquote>
+     *
+     * @param target the method handle to invoke after arguments are filtered
+     * @param pos the position of the first argument to filter
+     * @param filters method handles to call initially on filtered arguments
+     * @return method handle which incorporates the specified argument filtering logic
+     * @throws NullPointerException if the target is null
+     *                              or if the {@code filters} array is null
+     * @throws IllegalArgumentException if a non-null element of {@code filters}
+     *          does not match a corresponding argument type of target as described above,
+     *          or if the {@code pos+filters.length} is greater than {@code target.type().parameterCount()},
+     *          or if the resulting method handle's type would have
+     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
+     */
     public static
-    MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) { return null; }
+    MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters) {
+        filterArgumentsCheckArity(target, pos, filters);
 
+        for (int i = 0; i < filters.length; ++i) {
+            filterArgumentChecks(target, i + pos, filters[i]);
+        }
+
+        return new Transformers.FilterArguments(target, pos, filters);
+    }
+
+    private static void filterArgumentsCheckArity(MethodHandle target, int pos, MethodHandle[] filters) {
+        MethodType targetType = target.type();
+        int maxPos = targetType.parameterCount();
+        if (pos + filters.length > maxPos)
+            throw newIllegalArgumentException("too many filters");
+    }
+
+    private static void filterArgumentChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException {
+        MethodType targetType = target.type();
+        MethodType filterType = filter.type();
+        if (filterType.parameterCount() != 1
+            || filterType.returnType() != targetType.parameterType(pos))
+            throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
+    }
+
+    /**
+     * Adapts a target method handle by pre-processing
+     * a sub-sequence of its arguments with a filter (another method handle).
+     * The pre-processed arguments are replaced by the result (if any) of the
+     * filter function.
+     * The target is then called on the modified (usually shortened) argument list.
+     * <p>
+     * If the filter returns a value, the target must accept that value as
+     * its argument in position {@code pos}, preceded and/or followed by
+     * any arguments not passed to the filter.
+     * If the filter returns void, the target must accept all arguments
+     * not passed to the filter.
+     * No arguments are reordered, and a result returned from the filter
+     * replaces (in order) the whole subsequence of arguments originally
+     * passed to the adapter.
+     * <p>
+     * The argument types (if any) of the filter
+     * replace zero or one argument types of the target, at position {@code pos},
+     * in the resulting adapted method handle.
+     * The return type of the filter (if any) must be identical to the
+     * argument type of the target at position {@code pos}, and that target argument
+     * is supplied by the return value of the filter.
+     * <p>
+     * In all cases, {@code pos} must be greater than or equal to zero, and
+     * {@code pos} must also be less than or equal to the target's arity.
+     * <p><b>Example:</b>
+     * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle deepToString = publicLookup()
+  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+
+MethodHandle ts1 = deepToString.asCollector(String[].class, 1);
+assertEquals("[strange]", (String) ts1.invokeExact("strange"));
+
+MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
+assertEquals("[up, down]", (String) ts2.invokeExact("up", "down"));
+
+MethodHandle ts3 = deepToString.asCollector(String[].class, 3);
+MethodHandle ts3_ts2 = collectArguments(ts3, 1, ts2);
+assertEquals("[top, [up, down], strange]",
+             (String) ts3_ts2.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts1 = collectArguments(ts3_ts2, 3, ts1);
+assertEquals("[top, [up, down], [strange]]",
+             (String) ts3_ts2_ts1.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts3 = collectArguments(ts3_ts2, 1, ts3);
+assertEquals("[top, [[up, down, strange], charm], bottom]",
+             (String) ts3_ts2_ts3.invokeExact("top", "up", "down", "strange", "charm", "bottom"));
+     * }</pre></blockquote>
+     * <p> Here is pseudocode for the resulting adapter:
+     * <blockquote><pre>{@code
+     * T target(A...,V,C...);
+     * V filter(B...);
+     * T adapter(A... a,B... b,C... c) {
+     *   V v = filter(b...);
+     *   return target(a...,v,c...);
+     * }
+     * // and if the filter has no arguments:
+     * T target2(A...,V,C...);
+     * V filter2();
+     * T adapter2(A... a,C... c) {
+     *   V v = filter2();
+     *   return target2(a...,v,c...);
+     * }
+     * // and if the filter has a void return:
+     * T target3(A...,C...);
+     * void filter3(B...);
+     * void adapter3(A... a,B... b,C... c) {
+     *   filter3(b...);
+     *   return target3(a...,c...);
+     * }
+     * }</pre></blockquote>
+     * <p>
+     * A collection adapter {@code collectArguments(mh, 0, coll)} is equivalent to
+     * one which first "folds" the affected arguments, and then drops them, in separate
+     * steps as follows:
+     * <blockquote><pre>{@code
+     * mh = MethodHandles.dropArguments(mh, 1, coll.type().parameterList()); //step 2
+     * mh = MethodHandles.foldArguments(mh, coll); //step 1
+     * }</pre></blockquote>
+     * If the target method handle consumes no arguments besides than the result
+     * (if any) of the filter {@code coll}, then {@code collectArguments(mh, 0, coll)}
+     * is equivalent to {@code filterReturnValue(coll, mh)}.
+     * If the filter method handle {@code coll} consumes one argument and produces
+     * a non-void result, then {@code collectArguments(mh, N, coll)}
+     * is equivalent to {@code filterArguments(mh, N, coll)}.
+     * Other equivalences are possible but would require argument permutation.
+     *
+     * @param target the method handle to invoke after filtering the subsequence of arguments
+     * @param pos the position of the first adapter argument to pass to the filter,
+     *            and/or the target argument which receives the result of the filter
+     * @param filter method handle to call on the subsequence of arguments
+     * @return method handle which incorporates the specified argument subsequence filtering logic
+     * @throws NullPointerException if either argument is null
+     * @throws IllegalArgumentException if the return type of {@code filter}
+     *          is non-void and is not the same as the {@code pos} argument of the target,
+     *          or if {@code pos} is not between 0 and the target's arity, inclusive,
+     *          or if the resulting method handle's type would have
+     *          <a href="MethodHandle.html#maxarity">too many parameters</a>
+     * @see MethodHandles#foldArguments
+     * @see MethodHandles#filterArguments
+     * @see MethodHandles#filterReturnValue
+     */
     public static
-    MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) { return null; }
+    MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) {
+        MethodType newType = collectArgumentsChecks(target, pos, filter);
+        return new Transformers.CollectArguments(target, filter, pos, newType);
+    }
 
+    private static MethodType collectArgumentsChecks(MethodHandle target, int pos, MethodHandle filter) throws RuntimeException {
+        MethodType targetType = target.type();
+        MethodType filterType = filter.type();
+        Class<?> rtype = filterType.returnType();
+        List<Class<?>> filterArgs = filterType.parameterList();
+        if (rtype == void.class) {
+            return targetType.insertParameterTypes(pos, filterArgs);
+        }
+        if (rtype != targetType.parameterType(pos)) {
+            throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
+        }
+        return targetType.dropParameterTypes(pos, pos+1).insertParameterTypes(pos, filterArgs);
+    }
+
+    /**
+     * Adapts a target method handle by post-processing
+     * its return value (if any) with a filter (another method handle).
+     * The result of the filter is returned from the adapter.
+     * <p>
+     * If the target returns a value, the filter must accept that value as
+     * its only argument.
+     * If the target returns void, the filter must accept no arguments.
+     * <p>
+     * The return type of the filter
+     * replaces the return type of the target
+     * in the resulting adapted method handle.
+     * The argument type of the filter (if any) must be identical to the
+     * return type of the target.
+     * <p><b>Example:</b>
+     * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+MethodHandle length = lookup().findVirtual(String.class,
+  "length", methodType(int.class));
+System.out.println((String) cat.invokeExact("x", "y")); // xy
+MethodHandle f0 = filterReturnValue(cat, length);
+System.out.println((int) f0.invokeExact("x", "y")); // 2
+     * }</pre></blockquote>
+     * <p> Here is pseudocode for the resulting adapter:
+     * <blockquote><pre>{@code
+     * V target(A...);
+     * T filter(V);
+     * T adapter(A... a) {
+     *   V v = target(a...);
+     *   return filter(v);
+     * }
+     * // and if the target has a void return:
+     * void target2(A...);
+     * T filter2();
+     * T adapter2(A... a) {
+     *   target2(a...);
+     *   return filter2();
+     * }
+     * // and if the filter has a void return:
+     * V target3(A...);
+     * void filter3(V);
+     * void adapter3(A... a) {
+     *   V v = target3(a...);
+     *   filter3(v);
+     * }
+     * }</pre></blockquote>
+     * @param target the method handle to invoke before filtering the return value
+     * @param filter method handle to call on the return value
+     * @return method handle which incorporates the specified return value filtering logic
+     * @throws NullPointerException if either argument is null
+     * @throws IllegalArgumentException if the argument list of {@code filter}
+     *          does not match the return type of target as described above
+     */
     public static
-    MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) { return null; }
+    MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) {
+        MethodType targetType = target.type();
+        MethodType filterType = filter.type();
+        filterReturnValueChecks(targetType, filterType);
 
+        return new Transformers.FilterReturnValue(target, filter);
+    }
+
+    private static void filterReturnValueChecks(MethodType targetType, MethodType filterType) throws RuntimeException {
+        Class<?> rtype = targetType.returnType();
+        int filterValues = filterType.parameterCount();
+        if (filterValues == 0
+                ? (rtype != void.class)
+                : (rtype != filterType.parameterType(0) || filterValues != 1))
+            throw newIllegalArgumentException("target and filter types do not match", targetType, filterType);
+    }
+
+    /**
+     * Adapts a target method handle by pre-processing
+     * some of its arguments, and then calling the target with
+     * the result of the pre-processing, inserted into the original
+     * sequence of arguments.
+     * <p>
+     * The pre-processing is performed by {@code combiner}, a second method handle.
+     * Of the arguments passed to the adapter, the first {@code N} arguments
+     * are copied to the combiner, which is then called.
+     * (Here, {@code N} is defined as the parameter count of the combiner.)
+     * After this, control passes to the target, with any result
+     * from the combiner inserted before the original {@code N} incoming
+     * arguments.
+     * <p>
+     * If the combiner returns a value, the first parameter type of the target
+     * must be identical with the return type of the combiner, and the next
+     * {@code N} parameter types of the target must exactly match the parameters
+     * of the combiner.
+     * <p>
+     * If the combiner has a void return, no result will be inserted,
+     * and the first {@code N} parameter types of the target
+     * must exactly match the parameters of the combiner.
+     * <p>
+     * The resulting adapter is the same type as the target, except that the
+     * first parameter type is dropped,
+     * if it corresponds to the result of the combiner.
+     * <p>
+     * (Note that {@link #dropArguments(MethodHandle,int,List) dropArguments} can be used to remove any arguments
+     * that either the combiner or the target does not wish to receive.
+     * If some of the incoming arguments are destined only for the combiner,
+     * consider using {@link MethodHandle#asCollector asCollector} instead, since those
+     * arguments will not need to be live on the stack on entry to the
+     * target.)
+     * <p><b>Example:</b>
+     * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle trace = publicLookup().findVirtual(java.io.PrintStream.class,
+  "println", methodType(void.class, String.class))
+    .bindTo(System.out);
+MethodHandle cat = lookup().findVirtual(String.class,
+  "concat", methodType(String.class, String.class));
+assertEquals("boojum", (String) cat.invokeExact("boo", "jum"));
+MethodHandle catTrace = foldArguments(cat, trace);
+// also prints "boo":
+assertEquals("boojum", (String) catTrace.invokeExact("boo", "jum"));
+     * }</pre></blockquote>
+     * <p> Here is pseudocode for the resulting adapter:
+     * <blockquote><pre>{@code
+     * // there are N arguments in A...
+     * T target(V, A[N]..., B...);
+     * V combiner(A...);
+     * T adapter(A... a, B... b) {
+     *   V v = combiner(a...);
+     *   return target(v, a..., b...);
+     * }
+     * // and if the combiner has a void return:
+     * T target2(A[N]..., B...);
+     * void combiner2(A...);
+     * T adapter2(A... a, B... b) {
+     *   combiner2(a...);
+     *   return target2(a..., b...);
+     * }
+     * }</pre></blockquote>
+     * @param target the method handle to invoke after arguments are combined
+     * @param combiner method handle to call initially on the incoming arguments
+     * @return method handle which incorporates the specified argument folding logic
+     * @throws NullPointerException if either argument is null
+     * @throws IllegalArgumentException if {@code combiner}'s return type
+     *          is non-void and not the same as the first argument type of
+     *          the target, or if the initial {@code N} argument types
+     *          of the target
+     *          (skipping one matching the {@code combiner}'s return type)
+     *          are not identical with the argument types of {@code combiner}
+     */
     public static
-    MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) { return null; }
+    MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) {
+        int foldPos = 0;
+        MethodType targetType = target.type();
+        MethodType combinerType = combiner.type();
+        Class<?> rtype = foldArgumentChecks(foldPos, targetType, combinerType);
 
+        return new Transformers.FoldArguments(target, combiner);
+    }
+
+    private static Class<?> foldArgumentChecks(int foldPos, MethodType targetType, MethodType combinerType) {
+        int foldArgs   = combinerType.parameterCount();
+        Class<?> rtype = combinerType.returnType();
+        int foldVals = rtype == void.class ? 0 : 1;
+        int afterInsertPos = foldPos + foldVals;
+        boolean ok = (targetType.parameterCount() >= afterInsertPos + foldArgs);
+        if (ok && !(combinerType.parameterList()
+                    .equals(targetType.parameterList().subList(afterInsertPos,
+                                                               afterInsertPos + foldArgs))))
+            ok = false;
+        if (ok && foldVals != 0 && combinerType.returnType() != targetType.parameterType(0))
+            ok = false;
+        if (!ok)
+            throw misMatchedTypes("target and combiner types", targetType, combinerType);
+        return rtype;
+    }
+
+    /**
+     * Makes a method handle which adapts a target method handle,
+     * by guarding it with a test, a boolean-valued method handle.
+     * If the guard fails, a fallback handle is called instead.
+     * All three method handles must have the same corresponding
+     * argument and return types, except that the return type
+     * of the test must be boolean, and the test is allowed
+     * to have fewer arguments than the other two method handles.
+     * <p> Here is pseudocode for the resulting adapter:
+     * <blockquote><pre>{@code
+     * boolean test(A...);
+     * T target(A...,B...);
+     * T fallback(A...,B...);
+     * T adapter(A... a,B... b) {
+     *   if (test(a...))
+     *     return target(a..., b...);
+     *   else
+     *     return fallback(a..., b...);
+     * }
+     * }</pre></blockquote>
+     * Note that the test arguments ({@code a...} in the pseudocode) cannot
+     * be modified by execution of the test, and so are passed unchanged
+     * from the caller to the target or fallback as appropriate.
+     * @param test method handle used for test, must return boolean
+     * @param target method handle to call if test passes
+     * @param fallback method handle to call if test fails
+     * @return method handle which incorporates the specified if/then/else logic
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if {@code test} does not return boolean,
+     *          or if all three method types do not match (with the return
+     *          type of {@code test} changed to match that of the target).
+     */
     public static
     MethodHandle guardWithTest(MethodHandle test,
                                MethodHandle target,
-                               MethodHandle fallback) { return null; }
+                               MethodHandle fallback) {
+        MethodType gtype = test.type();
+        MethodType ttype = target.type();
+        MethodType ftype = fallback.type();
+        if (!ttype.equals(ftype))
+            throw misMatchedTypes("target and fallback types", ttype, ftype);
+        if (gtype.returnType() != boolean.class)
+            throw newIllegalArgumentException("guard type is not a predicate "+gtype);
+        List<Class<?>> targs = ttype.parameterList();
+        List<Class<?>> gargs = gtype.parameterList();
+        if (!targs.equals(gargs)) {
+            int gpc = gargs.size(), tpc = targs.size();
+            if (gpc >= tpc || !targs.subList(0, gpc).equals(gargs))
+                throw misMatchedTypes("target and test types", ttype, gtype);
+            test = dropArguments(test, gpc, targs.subList(gpc, tpc));
+            gtype = test.type();
+        }
 
+        return new Transformers.GuardWithTest(test, target, fallback);
+    }
+
+    static RuntimeException misMatchedTypes(String what, MethodType t1, MethodType t2) {
+        return newIllegalArgumentException(what + " must match: " + t1 + " != " + t2);
+    }
+
+    /**
+     * Makes a method handle which adapts a target method handle,
+     * by running it inside an exception handler.
+     * If the target returns normally, the adapter returns that value.
+     * If an exception matching the specified type is thrown, the fallback
+     * handle is called instead on the exception, plus the original arguments.
+     * <p>
+     * The target and handler must have the same corresponding
+     * argument and return types, except that handler may omit trailing arguments
+     * (similarly to the predicate in {@link #guardWithTest guardWithTest}).
+     * Also, the handler must have an extra leading parameter of {@code exType} or a supertype.
+     * <p> Here is pseudocode for the resulting adapter:
+     * <blockquote><pre>{@code
+     * T target(A..., B...);
+     * T handler(ExType, A...);
+     * T adapter(A... a, B... b) {
+     *   try {
+     *     return target(a..., b...);
+     *   } catch (ExType ex) {
+     *     return handler(ex, a...);
+     *   }
+     * }
+     * }</pre></blockquote>
+     * Note that the saved arguments ({@code a...} in the pseudocode) cannot
+     * be modified by execution of the target, and so are passed unchanged
+     * from the caller to the handler, if the handler is invoked.
+     * <p>
+     * The target and handler must return the same type, even if the handler
+     * always throws.  (This might happen, for instance, because the handler
+     * is simulating a {@code finally} clause).
+     * To create such a throwing handler, compose the handler creation logic
+     * with {@link #throwException throwException},
+     * in order to create a method handle of the correct return type.
+     * @param target method handle to call
+     * @param exType the type of exception which the handler will catch
+     * @param handler method handle to call if a matching exception is thrown
+     * @return method handle which incorporates the specified try/catch logic
+     * @throws NullPointerException if any argument is null
+     * @throws IllegalArgumentException if {@code handler} does not accept
+     *          the given exception type, or if the method handle types do
+     *          not match in their return types and their
+     *          corresponding parameters
+     */
     public static
     MethodHandle catchException(MethodHandle target,
                                 Class<? extends Throwable> exType,
-                                MethodHandle handler) { return null; }
+                                MethodHandle handler) {
+        MethodType ttype = target.type();
+        MethodType htype = handler.type();
+        if (htype.parameterCount() < 1 ||
+            !htype.parameterType(0).isAssignableFrom(exType))
+            throw newIllegalArgumentException("handler does not accept exception type "+exType);
+        if (htype.returnType() != ttype.returnType())
+            throw misMatchedTypes("target and handler return types", ttype, htype);
+        List<Class<?>> targs = ttype.parameterList();
+        List<Class<?>> hargs = htype.parameterList();
+        hargs = hargs.subList(1, hargs.size());  // omit leading parameter from handler
+        if (!targs.equals(hargs)) {
+            int hpc = hargs.size(), tpc = targs.size();
+            if (hpc >= tpc || !targs.subList(0, hpc).equals(hargs))
+                throw misMatchedTypes("target and handler types", ttype, htype);
+        }
 
+        return new Transformers.CatchException(target, handler, exType);
+    }
+
+    /**
+     * Produces a method handle which will throw exceptions of the given {@code exType}.
+     * The method handle will accept a single argument of {@code exType},
+     * and immediately throw it as an exception.
+     * The method type will nominally specify a return of {@code returnType}.
+     * The return type may be anything convenient:  It doesn't matter to the
+     * method handle's behavior, since it will never return normally.
+     * @param returnType the return type of the desired method handle
+     * @param exType the parameter type of the desired method handle
+     * @return method handle which can throw the given exceptions
+     * @throws NullPointerException if either argument is null
+     */
     public static
-    MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) { return null; }
+    MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType) {
+        if (!Throwable.class.isAssignableFrom(exType))
+            throw new ClassCastException(exType.getName());
+
+        return new Transformers.AlwaysThrow(returnType, exType);
+    }
 }
diff --git a/java/lang/invoke/MethodType.java b/java/lang/invoke/MethodType.java
index 4cb5c22..bfa7ccd 100644
--- a/java/lang/invoke/MethodType.java
+++ b/java/lang/invoke/MethodType.java
@@ -25,78 +25,1227 @@
 
 package java.lang.invoke;
 
+import sun.invoke.util.Wrapper;
+import java.lang.ref.WeakReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+import sun.invoke.util.BytecodeDescriptor;
+import static java.lang.invoke.MethodHandleStatics.*;
 
+/**
+ * A method type represents the arguments and return type accepted and
+ * returned by a method handle, or the arguments and return type passed
+ * and expected  by a method handle caller.  Method types must be properly
+ * matched between a method handle and all its callers,
+ * and the JVM's operations enforce this matching at, specifically
+ * during calls to {@link MethodHandle#invokeExact MethodHandle.invokeExact}
+ * and {@link MethodHandle#invoke MethodHandle.invoke}, and during execution
+ * of {@code invokedynamic} instructions.
+ * <p>
+ * The structure is a return type accompanied by any number of parameter types.
+ * The types (primitive, {@code void}, and reference) are represented by {@link Class} objects.
+ * (For ease of exposition, we treat {@code void} as if it were a type.
+ * In fact, it denotes the absence of a return type.)
+ * <p>
+ * All instances of {@code MethodType} are immutable.
+ * Two instances are completely interchangeable if they compare equal.
+ * Equality depends on pairwise correspondence of the return and parameter types and on nothing else.
+ * <p>
+ * This type can be created only by factory methods.
+ * All factory methods may cache values, though caching is not guaranteed.
+ * Some factory methods are static, while others are virtual methods which
+ * modify precursor method types, e.g., by changing a selected parameter.
+ * <p>
+ * Factory methods which operate on groups of parameter types
+ * are systematically presented in two versions, so that both Java arrays and
+ * Java lists can be used to work with groups of parameter types.
+ * The query methods {@code parameterArray} and {@code parameterList}
+ * also provide a choice between arrays and lists.
+ * <p>
+ * {@code MethodType} objects are sometimes derived from bytecode instructions
+ * such as {@code invokedynamic}, specifically from the type descriptor strings associated
+ * with the instructions in a class file's constant pool.
+ * <p>
+ * Like classes and strings, method types can also be represented directly
+ * in a class file's constant pool as constants.
+ * A method type may be loaded by an {@code ldc} instruction which refers
+ * to a suitable {@code CONSTANT_MethodType} constant pool entry.
+ * The entry refers to a {@code CONSTANT_Utf8} spelling for the descriptor string.
+ * (For full details on method type constants,
+ * see sections 4.4.8 and 5.4.3.5 of the Java Virtual Machine Specification.)
+ * <p>
+ * When the JVM materializes a {@code MethodType} from a descriptor string,
+ * all classes named in the descriptor must be accessible, and will be loaded.
+ * (But the classes need not be initialized, as is the case with a {@code CONSTANT_Class}.)
+ * This loading may occur at any time before the {@code MethodType} object is first derived.
+ * @author John Rose, JSR 292 EG
+ */
 public final
 class MethodType implements java.io.Serializable {
+    private static final long serialVersionUID = 292L;  // {rtype, {ptype...}}
 
+    // The rtype and ptypes fields define the structural identity of the method type:
+    private final Class<?>   rtype;
+    private final Class<?>[] ptypes;
+
+    // The remaining fields are caches of various sorts:
+    private @Stable MethodTypeForm form; // erased form, plus cached data about primitives
+    private @Stable MethodType wrapAlt;  // alternative wrapped/unwrapped version
+    // Android-changed: Remove adapter cache. We're not dynamically generating any
+    // adapters at this point.
+    // private @Stable Invokers invokers;   // cache of handy higher-order adapters
+    private @Stable String methodDescriptor;  // cache for toMethodDescriptorString
+
+    /**
+     * Check the given parameters for validity and store them into the final fields.
+     */
+    private MethodType(Class<?> rtype, Class<?>[] ptypes, boolean trusted) {
+        checkRtype(rtype);
+        checkPtypes(ptypes);
+        this.rtype = rtype;
+        // defensively copy the array passed in by the user
+        this.ptypes = trusted ? ptypes : Arrays.copyOf(ptypes, ptypes.length);
+    }
+
+    /**
+     * Construct a temporary unchecked instance of MethodType for use only as a key to the intern table.
+     * Does not check the given parameters for validity, and must be discarded after it is used as a searching key.
+     * The parameters are reversed for this constructor, so that is is not accidentally used.
+     */
+    private MethodType(Class<?>[] ptypes, Class<?> rtype) {
+        this.rtype = rtype;
+        this.ptypes = ptypes;
+    }
+
+    /*trusted*/ MethodTypeForm form() { return form; }
+    /*trusted*/ /** @hide */ public Class<?> rtype() { return rtype; }
+    /*trusted*/ /** @hide */ public Class<?>[] ptypes() { return ptypes; }
+
+    // Android-changed: Removed method setForm. It's unused in the JDK and there's no
+    // good reason to allow the form to be set externally.
+    //
+    // void setForm(MethodTypeForm f) { form = f; }
+
+    /** This number, mandated by the JVM spec as 255,
+     *  is the maximum number of <em>slots</em>
+     *  that any Java method can receive in its argument list.
+     *  It limits both JVM signatures and method type objects.
+     *  The longest possible invocation will look like
+     *  {@code staticMethod(arg1, arg2, ..., arg255)} or
+     *  {@code x.virtualMethod(arg1, arg2, ..., arg254)}.
+     */
+    /*non-public*/ static final int MAX_JVM_ARITY = 255;  // this is mandated by the JVM spec.
+
+    /** This number is the maximum arity of a method handle, 254.
+     *  It is derived from the absolute JVM-imposed arity by subtracting one,
+     *  which is the slot occupied by the method handle itself at the
+     *  beginning of the argument list used to invoke the method handle.
+     *  The longest possible invocation will look like
+     *  {@code mh.invoke(arg1, arg2, ..., arg254)}.
+     */
+    // Issue:  Should we allow MH.invokeWithArguments to go to the full 255?
+    /*non-public*/ static final int MAX_MH_ARITY = MAX_JVM_ARITY-1;  // deduct one for mh receiver
+
+    /** This number is the maximum arity of a method handle invoker, 253.
+     *  It is derived from the absolute JVM-imposed arity by subtracting two,
+     *  which are the slots occupied by invoke method handle, and the
+     *  target method handle, which are both at the beginning of the argument
+     *  list used to invoke the target method handle.
+     *  The longest possible invocation will look like
+     *  {@code invokermh.invoke(targetmh, arg1, arg2, ..., arg253)}.
+     */
+    /*non-public*/ static final int MAX_MH_INVOKER_ARITY = MAX_MH_ARITY-1;  // deduct one more for invoker
+
+    private static void checkRtype(Class<?> rtype) {
+        Objects.requireNonNull(rtype);
+    }
+    private static void checkPtype(Class<?> ptype) {
+        Objects.requireNonNull(ptype);
+        if (ptype == void.class)
+            throw newIllegalArgumentException("parameter type cannot be void");
+    }
+    /** Return number of extra slots (count of long/double args). */
+    private static int checkPtypes(Class<?>[] ptypes) {
+        int slots = 0;
+        for (Class<?> ptype : ptypes) {
+            checkPtype(ptype);
+            if (ptype == double.class || ptype == long.class) {
+                slots++;
+            }
+        }
+        checkSlotCount(ptypes.length + slots);
+        return slots;
+    }
+    static void checkSlotCount(int count) {
+        assert((MAX_JVM_ARITY & (MAX_JVM_ARITY+1)) == 0);
+        // MAX_JVM_ARITY must be power of 2 minus 1 for following code trick to work:
+        if ((count & MAX_JVM_ARITY) != count)
+            throw newIllegalArgumentException("bad parameter count "+count);
+    }
+    private static IndexOutOfBoundsException newIndexOutOfBoundsException(Object num) {
+        if (num instanceof Integer)  num = "bad index: "+num;
+        return new IndexOutOfBoundsException(num.toString());
+    }
+
+    static final ConcurrentWeakInternSet<MethodType> internTable = new ConcurrentWeakInternSet<>();
+
+    static final Class<?>[] NO_PTYPES = {};
+
+    /**
+     * Finds or creates an instance of the given method type.
+     * @param rtype  the return type
+     * @param ptypes the parameter types
+     * @return a method type with the given components
+     * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null
+     * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class}
+     */
     public static
     MethodType methodType(Class<?> rtype, Class<?>[] ptypes) {
-        return null;
+        return makeImpl(rtype, ptypes, false);
     }
 
+    /**
+     * Finds or creates a method type with the given components.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * @param rtype  the return type
+     * @param ptypes the parameter types
+     * @return a method type with the given components
+     * @throws NullPointerException if {@code rtype} or {@code ptypes} or any element of {@code ptypes} is null
+     * @throws IllegalArgumentException if any element of {@code ptypes} is {@code void.class}
+     */
     public static
     MethodType methodType(Class<?> rtype, List<Class<?>> ptypes) {
-        return null;
+        boolean notrust = false;  // random List impl. could return evil ptypes array
+        return makeImpl(rtype, listToArray(ptypes), notrust);
     }
 
+    private static Class<?>[] listToArray(List<Class<?>> ptypes) {
+        // sanity check the size before the toArray call, since size might be huge
+        checkSlotCount(ptypes.size());
+        return ptypes.toArray(NO_PTYPES);
+    }
+
+    /**
+     * Finds or creates a method type with the given components.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * The leading parameter type is prepended to the remaining array.
+     * @param rtype  the return type
+     * @param ptype0 the first parameter type
+     * @param ptypes the remaining parameter types
+     * @return a method type with the given components
+     * @throws NullPointerException if {@code rtype} or {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is null
+     * @throws IllegalArgumentException if {@code ptype0} or {@code ptypes} or any element of {@code ptypes} is {@code void.class}
+     */
     public static
-    MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) { return null; }
+    MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes) {
+        Class<?>[] ptypes1 = new Class<?>[1+ptypes.length];
+        ptypes1[0] = ptype0;
+        System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length);
+        return makeImpl(rtype, ptypes1, true);
+    }
 
+    /**
+     * Finds or creates a method type with the given components.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * The resulting method has no parameter types.
+     * @param rtype  the return type
+     * @return a method type with the given return value
+     * @throws NullPointerException if {@code rtype} is null
+     */
     public static
-    MethodType methodType(Class<?> rtype) { return null; }
+    MethodType methodType(Class<?> rtype) {
+        return makeImpl(rtype, NO_PTYPES, true);
+    }
 
+    /**
+     * Finds or creates a method type with the given components.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * The resulting method has the single given parameter type.
+     * @param rtype  the return type
+     * @param ptype0 the parameter type
+     * @return a method type with the given return value and parameter type
+     * @throws NullPointerException if {@code rtype} or {@code ptype0} is null
+     * @throws IllegalArgumentException if {@code ptype0} is {@code void.class}
+     */
     public static
-    MethodType methodType(Class<?> rtype, Class<?> ptype0) { return null; }
+    MethodType methodType(Class<?> rtype, Class<?> ptype0) {
+        return makeImpl(rtype, new Class<?>[]{ ptype0 }, true);
+    }
 
+    /**
+     * Finds or creates a method type with the given components.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * The resulting method has the same parameter types as {@code ptypes},
+     * and the specified return type.
+     * @param rtype  the return type
+     * @param ptypes the method type which supplies the parameter types
+     * @return a method type with the given components
+     * @throws NullPointerException if {@code rtype} or {@code ptypes} is null
+     */
     public static
-    MethodType methodType(Class<?> rtype, MethodType ptypes) { return null; }
+    MethodType methodType(Class<?> rtype, MethodType ptypes) {
+        return makeImpl(rtype, ptypes.ptypes, true);
+    }
 
+    /**
+     * Sole factory method to find or create an interned method type.
+     * @param rtype desired return type
+     * @param ptypes desired parameter types
+     * @param trusted whether the ptypes can be used without cloning
+     * @return the unique method type of the desired structure
+     */
+    /*trusted*/ static
+    MethodType makeImpl(Class<?> rtype, Class<?>[] ptypes, boolean trusted) {
+        MethodType mt = internTable.get(new MethodType(ptypes, rtype));
+        if (mt != null)
+            return mt;
+        if (ptypes.length == 0) {
+            ptypes = NO_PTYPES; trusted = true;
+        }
+        mt = new MethodType(rtype, ptypes, trusted);
+        // promote the object to the Real Thing, and reprobe
+        mt.form = MethodTypeForm.findForm(mt);
+        return internTable.add(mt);
+    }
+    private static final MethodType[] objectOnlyTypes = new MethodType[20];
+
+    /**
+     * Finds or creates a method type whose components are {@code Object} with an optional trailing {@code Object[]} array.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * All parameters and the return type will be {@code Object},
+     * except the final array parameter if any, which will be {@code Object[]}.
+     * @param objectArgCount number of parameters (excluding the final array parameter if any)
+     * @param finalArray whether there will be a trailing array parameter, of type {@code Object[]}
+     * @return a generally applicable method type, for all calls of the given fixed argument count and a collected array of further arguments
+     * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255 (or 254, if {@code finalArray} is true)
+     * @see #genericMethodType(int)
+     */
     public static
-    MethodType genericMethodType(int objectArgCount, boolean finalArray) { return null; }
+    MethodType genericMethodType(int objectArgCount, boolean finalArray) {
+        MethodType mt;
+        checkSlotCount(objectArgCount);
+        int ivarargs = (!finalArray ? 0 : 1);
+        int ootIndex = objectArgCount*2 + ivarargs;
+        if (ootIndex < objectOnlyTypes.length) {
+            mt = objectOnlyTypes[ootIndex];
+            if (mt != null)  return mt;
+        }
+        Class<?>[] ptypes = new Class<?>[objectArgCount + ivarargs];
+        Arrays.fill(ptypes, Object.class);
+        if (ivarargs != 0)  ptypes[objectArgCount] = Object[].class;
+        mt = makeImpl(Object.class, ptypes, true);
+        if (ootIndex < objectOnlyTypes.length) {
+            objectOnlyTypes[ootIndex] = mt;     // cache it here also!
+        }
+        return mt;
+    }
 
+    /**
+     * Finds or creates a method type whose components are all {@code Object}.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * All parameters and the return type will be Object.
+     * @param objectArgCount number of parameters
+     * @return a generally applicable method type, for all calls of the given argument count
+     * @throws IllegalArgumentException if {@code objectArgCount} is negative or greater than 255
+     * @see #genericMethodType(int, boolean)
+     */
     public static
-    MethodType genericMethodType(int objectArgCount) { return null; }
+    MethodType genericMethodType(int objectArgCount) {
+        return genericMethodType(objectArgCount, false);
+    }
 
-    public MethodType changeParameterType(int num, Class<?> nptype) { return null; }
+    /**
+     * Finds or creates a method type with a single different parameter type.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * @param num    the index (zero-based) of the parameter type to change
+     * @param nptype a new parameter type to replace the old one with
+     * @return the same type, except with the selected parameter changed
+     * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()}
+     * @throws IllegalArgumentException if {@code nptype} is {@code void.class}
+     * @throws NullPointerException if {@code nptype} is null
+     */
+    public MethodType changeParameterType(int num, Class<?> nptype) {
+        if (parameterType(num) == nptype)  return this;
+        checkPtype(nptype);
+        Class<?>[] nptypes = ptypes.clone();
+        nptypes[num] = nptype;
+        return makeImpl(rtype, nptypes, true);
+    }
 
-    public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) { return null; }
+    /**
+     * Finds or creates a method type with additional parameter types.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * @param num    the position (zero-based) of the inserted parameter type(s)
+     * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
+     * @return the same type, except with the selected parameter(s) inserted
+     * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()}
+     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+     *                                  or if the resulting method type would have more than 255 parameter slots
+     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+     */
+    public MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert) {
+        int len = ptypes.length;
+        if (num < 0 || num > len)
+            throw newIndexOutOfBoundsException(num);
+        int ins = checkPtypes(ptypesToInsert);
+        checkSlotCount(parameterSlotCount() + ptypesToInsert.length + ins);
+        int ilen = ptypesToInsert.length;
+        if (ilen == 0)  return this;
+        Class<?>[] nptypes = Arrays.copyOfRange(ptypes, 0, len+ilen);
+        System.arraycopy(nptypes, num, nptypes, num+ilen, len-num);
+        System.arraycopy(ptypesToInsert, 0, nptypes, num, ilen);
+        return makeImpl(rtype, nptypes, true);
+    }
 
-    public MethodType appendParameterTypes(Class<?>... ptypesToInsert) { return null; }
+    /**
+     * Finds or creates a method type with additional parameter types.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list
+     * @return the same type, except with the selected parameter(s) appended
+     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+     *                                  or if the resulting method type would have more than 255 parameter slots
+     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+     */
+    public MethodType appendParameterTypes(Class<?>... ptypesToInsert) {
+        return insertParameterTypes(parameterCount(), ptypesToInsert);
+    }
 
-    public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) { return null; }
+    /**
+     * Finds or creates a method type with additional parameter types.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * @param num    the position (zero-based) of the inserted parameter type(s)
+     * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
+     * @return the same type, except with the selected parameter(s) inserted
+     * @throws IndexOutOfBoundsException if {@code num} is negative or greater than {@code parameterCount()}
+     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+     *                                  or if the resulting method type would have more than 255 parameter slots
+     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+     */
+    public MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert) {
+        return insertParameterTypes(num, listToArray(ptypesToInsert));
+    }
 
-    public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) { return null; }
+    /**
+     * Finds or creates a method type with additional parameter types.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * @param ptypesToInsert zero or more new parameter types to insert after the end of the parameter list
+     * @return the same type, except with the selected parameter(s) appended
+     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+     *                                  or if the resulting method type would have more than 255 parameter slots
+     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+     */
+    public MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) {
+        return insertParameterTypes(parameterCount(), ptypesToInsert);
+    }
 
-    public MethodType dropParameterTypes(int start, int end) { return null; }
+     /**
+     * Finds or creates a method type with modified parameter types.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * @param start  the position (zero-based) of the first replaced parameter type(s)
+     * @param end    the position (zero-based) after the last replaced parameter type(s)
+     * @param ptypesToInsert zero or more new parameter types to insert into the parameter list
+     * @return the same type, except with the selected parameter(s) replaced
+     * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()}
+     *                                  or if {@code end} is negative or greater than {@code parameterCount()}
+     *                                  or if {@code start} is greater than {@code end}
+     * @throws IllegalArgumentException if any element of {@code ptypesToInsert} is {@code void.class}
+     *                                  or if the resulting method type would have more than 255 parameter slots
+     * @throws NullPointerException if {@code ptypesToInsert} or any of its elements is null
+     */
+    /*non-public*/ MethodType replaceParameterTypes(int start, int end, Class<?>... ptypesToInsert) {
+        if (start == end)
+            return insertParameterTypes(start, ptypesToInsert);
+        int len = ptypes.length;
+        if (!(0 <= start && start <= end && end <= len))
+            throw newIndexOutOfBoundsException("start="+start+" end="+end);
+        int ilen = ptypesToInsert.length;
+        if (ilen == 0)
+            return dropParameterTypes(start, end);
+        return dropParameterTypes(start, end).insertParameterTypes(start, ptypesToInsert);
+    }
 
-    public MethodType changeReturnType(Class<?> nrtype) { return null; }
+    /** Replace the last arrayLength parameter types with the component type of arrayType.
+     * @param arrayType any array type
+     * @param arrayLength the number of parameter types to change
+     * @return the resulting type
+     */
+    /*non-public*/ MethodType asSpreaderType(Class<?> arrayType, int arrayLength) {
+        assert(parameterCount() >= arrayLength);
+        int spreadPos = ptypes.length - arrayLength;
+        if (arrayLength == 0)  return this;  // nothing to change
+        if (arrayType == Object[].class) {
+            if (isGeneric())  return this;  // nothing to change
+            if (spreadPos == 0) {
+                // no leading arguments to preserve; go generic
+                MethodType res = genericMethodType(arrayLength);
+                if (rtype != Object.class) {
+                    res = res.changeReturnType(rtype);
+                }
+                return res;
+            }
+        }
+        Class<?> elemType = arrayType.getComponentType();
+        assert(elemType != null);
+        for (int i = spreadPos; i < ptypes.length; i++) {
+            if (ptypes[i] != elemType) {
+                Class<?>[] fixedPtypes = ptypes.clone();
+                Arrays.fill(fixedPtypes, i, ptypes.length, elemType);
+                return methodType(rtype, fixedPtypes);
+            }
+        }
+        return this;  // arguments check out; no change
+    }
 
-    public boolean hasPrimitives() { return false; }
+    /** Return the leading parameter type, which must exist and be a reference.
+     *  @return the leading parameter type, after error checks
+     */
+    /*non-public*/ Class<?> leadingReferenceParameter() {
+        Class<?> ptype;
+        if (ptypes.length == 0 ||
+            (ptype = ptypes[0]).isPrimitive())
+            throw newIllegalArgumentException("no leading reference parameter");
+        return ptype;
+    }
 
-    public boolean hasWrappers() { return false; }
+    /** Delete the last parameter type and replace it with arrayLength copies of the component type of arrayType.
+     * @param arrayType any array type
+     * @param arrayLength the number of parameter types to insert
+     * @return the resulting type
+     */
+    /*non-public*/ MethodType asCollectorType(Class<?> arrayType, int arrayLength) {
+        assert(parameterCount() >= 1);
+        assert(lastParameterType().isAssignableFrom(arrayType));
+        MethodType res;
+        if (arrayType == Object[].class) {
+            res = genericMethodType(arrayLength);
+            if (rtype != Object.class) {
+                res = res.changeReturnType(rtype);
+            }
+        } else {
+            Class<?> elemType = arrayType.getComponentType();
+            assert(elemType != null);
+            res = methodType(rtype, Collections.nCopies(arrayLength, elemType));
+        }
+        if (ptypes.length == 1) {
+            return res;
+        } else {
+            return res.insertParameterTypes(0, parameterList().subList(0, ptypes.length-1));
+        }
+    }
 
-    public MethodType erase() { return null; }
+    /**
+     * Finds or creates a method type with some parameter types omitted.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * @param start  the index (zero-based) of the first parameter type to remove
+     * @param end    the index (greater than {@code start}) of the first parameter type after not to remove
+     * @return the same type, except with the selected parameter(s) removed
+     * @throws IndexOutOfBoundsException if {@code start} is negative or greater than {@code parameterCount()}
+     *                                  or if {@code end} is negative or greater than {@code parameterCount()}
+     *                                  or if {@code start} is greater than {@code end}
+     */
+    public MethodType dropParameterTypes(int start, int end) {
+        int len = ptypes.length;
+        if (!(0 <= start && start <= end && end <= len))
+            throw newIndexOutOfBoundsException("start="+start+" end="+end);
+        if (start == end)  return this;
+        Class<?>[] nptypes;
+        if (start == 0) {
+            if (end == len) {
+                // drop all parameters
+                nptypes = NO_PTYPES;
+            } else {
+                // drop initial parameter(s)
+                nptypes = Arrays.copyOfRange(ptypes, end, len);
+            }
+        } else {
+            if (end == len) {
+                // drop trailing parameter(s)
+                nptypes = Arrays.copyOfRange(ptypes, 0, start);
+            } else {
+                int tail = len - end;
+                nptypes = Arrays.copyOfRange(ptypes, 0, start + tail);
+                System.arraycopy(ptypes, end, nptypes, start, tail);
+            }
+        }
+        return makeImpl(rtype, nptypes, true);
+    }
 
-    public MethodType generic() { return null; }
+    /**
+     * Finds or creates a method type with a different return type.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * @param nrtype a return parameter type to replace the old one with
+     * @return the same type, except with the return type change
+     * @throws NullPointerException if {@code nrtype} is null
+     */
+    public MethodType changeReturnType(Class<?> nrtype) {
+        if (returnType() == nrtype)  return this;
+        return makeImpl(nrtype, ptypes, true);
+    }
 
-    public MethodType wrap() { return null; }
+    /**
+     * Reports if this type contains a primitive argument or return value.
+     * The return type {@code void} counts as a primitive.
+     * @return true if any of the types are primitives
+     */
+    public boolean hasPrimitives() {
+        return form.hasPrimitives();
+    }
 
-    public MethodType unwrap() { return null; }
+    /**
+     * Reports if this type contains a wrapper argument or return value.
+     * Wrappers are types which box primitive values, such as {@link Integer}.
+     * The reference type {@code java.lang.Void} counts as a wrapper,
+     * if it occurs as a return type.
+     * @return true if any of the types are wrappers
+     */
+    public boolean hasWrappers() {
+        return unwrap() != this;
+    }
 
-    public Class<?> parameterType(int num) { return null; }
+    /**
+     * Erases all reference types to {@code Object}.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * All primitive types (including {@code void}) will remain unchanged.
+     * @return a version of the original type with all reference types replaced
+     */
+    public MethodType erase() {
+        return form.erasedType();
+    }
 
-    public int parameterCount() { return 0; }
+    /**
+     * Erases all reference types to {@code Object}, and all subword types to {@code int}.
+     * This is the reduced type polymorphism used by private methods
+     * such as {@link MethodHandle#invokeBasic invokeBasic}.
+     * @return a version of the original type with all reference and subword types replaced
+     */
+    /*non-public*/ MethodType basicType() {
+        return form.basicType();
+    }
 
-    public Class<?> returnType() { return null; }
+    /**
+     * @return a version of the original type with MethodHandle prepended as the first argument
+     */
+    /*non-public*/ MethodType invokerType() {
+        return insertParameterTypes(0, MethodHandle.class);
+    }
 
-    public List<Class<?>> parameterList() { return null; }
+    /**
+     * Converts all types, both reference and primitive, to {@code Object}.
+     * Convenience method for {@link #genericMethodType(int) genericMethodType}.
+     * The expression {@code type.wrap().erase()} produces the same value
+     * as {@code type.generic()}.
+     * @return a version of the original type with all types replaced
+     */
+    public MethodType generic() {
+        return genericMethodType(parameterCount());
+    }
 
-    public Class<?>[] parameterArray() { return null; }
+    /*non-public*/ boolean isGeneric() {
+        return this == erase() && !hasPrimitives();
+    }
 
+    /**
+     * Converts all primitive types to their corresponding wrapper types.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * All reference types (including wrapper types) will remain unchanged.
+     * A {@code void} return type is changed to the type {@code java.lang.Void}.
+     * The expression {@code type.wrap().erase()} produces the same value
+     * as {@code type.generic()}.
+     * @return a version of the original type with all primitive types replaced
+     */
+    public MethodType wrap() {
+        return hasPrimitives() ? wrapWithPrims(this) : this;
+    }
+
+    /**
+     * Converts all wrapper types to their corresponding primitive types.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * All primitive types (including {@code void}) will remain unchanged.
+     * A return type of {@code java.lang.Void} is changed to {@code void}.
+     * @return a version of the original type with all wrapper types replaced
+     */
+    public MethodType unwrap() {
+        MethodType noprims = !hasPrimitives() ? this : wrapWithPrims(this);
+        return unwrapWithNoPrims(noprims);
+    }
+
+    private static MethodType wrapWithPrims(MethodType pt) {
+        assert(pt.hasPrimitives());
+        MethodType wt = pt.wrapAlt;
+        if (wt == null) {
+            // fill in lazily
+            wt = MethodTypeForm.canonicalize(pt, MethodTypeForm.WRAP, MethodTypeForm.WRAP);
+            assert(wt != null);
+            pt.wrapAlt = wt;
+        }
+        return wt;
+    }
+
+    private static MethodType unwrapWithNoPrims(MethodType wt) {
+        assert(!wt.hasPrimitives());
+        MethodType uwt = wt.wrapAlt;
+        if (uwt == null) {
+            // fill in lazily
+            uwt = MethodTypeForm.canonicalize(wt, MethodTypeForm.UNWRAP, MethodTypeForm.UNWRAP);
+            if (uwt == null)
+                uwt = wt;    // type has no wrappers or prims at all
+            wt.wrapAlt = uwt;
+        }
+        return uwt;
+    }
+
+    /**
+     * Returns the parameter type at the specified index, within this method type.
+     * @param num the index (zero-based) of the desired parameter type
+     * @return the selected parameter type
+     * @throws IndexOutOfBoundsException if {@code num} is not a valid index into {@code parameterArray()}
+     */
+    public Class<?> parameterType(int num) {
+        return ptypes[num];
+    }
+    /**
+     * Returns the number of parameter types in this method type.
+     * @return the number of parameter types
+     */
+    public int parameterCount() {
+        return ptypes.length;
+    }
+    /**
+     * Returns the return type of this method type.
+     * @return the return type
+     */
+    public Class<?> returnType() {
+        return rtype;
+    }
+
+    /**
+     * Presents the parameter types as a list (a convenience method).
+     * The list will be immutable.
+     * @return the parameter types (as an immutable list)
+     */
+    public List<Class<?>> parameterList() {
+        return Collections.unmodifiableList(Arrays.asList(ptypes.clone()));
+    }
+
+    /*non-public*/ Class<?> lastParameterType() {
+        int len = ptypes.length;
+        return len == 0 ? void.class : ptypes[len-1];
+    }
+
+    /**
+     * Presents the parameter types as an array (a convenience method).
+     * Changes to the array will not result in changes to the type.
+     * @return the parameter types (as a fresh copy if necessary)
+     */
+    public Class<?>[] parameterArray() {
+        return ptypes.clone();
+    }
+
+    /**
+     * Compares the specified object with this type for equality.
+     * That is, it returns <tt>true</tt> if and only if the specified object
+     * is also a method type with exactly the same parameters and return type.
+     * @param x object to compare
+     * @see Object#equals(Object)
+     */
+    @Override
+    public boolean equals(Object x) {
+        return this == x || x instanceof MethodType && equals((MethodType)x);
+    }
+
+    private boolean equals(MethodType that) {
+        return this.rtype == that.rtype
+            && Arrays.equals(this.ptypes, that.ptypes);
+    }
+
+    /**
+     * Returns the hash code value for this method type.
+     * It is defined to be the same as the hashcode of a List
+     * whose elements are the return type followed by the
+     * parameter types.
+     * @return the hash code value for this method type
+     * @see Object#hashCode()
+     * @see #equals(Object)
+     * @see List#hashCode()
+     */
+    @Override
+    public int hashCode() {
+      int hashCode = 31 + rtype.hashCode();
+      for (Class<?> ptype : ptypes)
+          hashCode = 31*hashCode + ptype.hashCode();
+      return hashCode;
+    }
+
+    /**
+     * Returns a string representation of the method type,
+     * of the form {@code "(PT0,PT1...)RT"}.
+     * The string representation of a method type is a
+     * parenthesis enclosed, comma separated list of type names,
+     * followed immediately by the return type.
+     * <p>
+     * Each type is represented by its
+     * {@link java.lang.Class#getSimpleName simple name}.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("(");
+        for (int i = 0; i < ptypes.length; i++) {
+            if (i > 0)  sb.append(",");
+            sb.append(ptypes[i].getSimpleName());
+        }
+        sb.append(")");
+        sb.append(rtype.getSimpleName());
+        return sb.toString();
+    }
+
+    /** True if the old return type can always be viewed (w/o casting) under new return type,
+     *  and the new parameters can be viewed (w/o casting) under the old parameter types.
+     */
+    // Android-changed: Removed implementation details.
+    // boolean isViewableAs(MethodType newType, boolean keepInterfaces);
+    // boolean parametersAreViewableAs(MethodType newType, boolean keepInterfaces);
+    /*non-public*/
+    boolean isConvertibleTo(MethodType newType) {
+        MethodTypeForm oldForm = this.form();
+        MethodTypeForm newForm = newType.form();
+        if (oldForm == newForm)
+            // same parameter count, same primitive/object mix
+            return true;
+        if (!canConvert(returnType(), newType.returnType()))
+            return false;
+        Class<?>[] srcTypes = newType.ptypes;
+        Class<?>[] dstTypes = ptypes;
+        if (srcTypes == dstTypes)
+            return true;
+        int argc;
+        if ((argc = srcTypes.length) != dstTypes.length)
+            return false;
+        if (argc <= 1) {
+            if (argc == 1 && !canConvert(srcTypes[0], dstTypes[0]))
+                return false;
+            return true;
+        }
+        if ((oldForm.primitiveParameterCount() == 0 && oldForm.erasedType == this) ||
+            (newForm.primitiveParameterCount() == 0 && newForm.erasedType == newType)) {
+            // Somewhat complicated test to avoid a loop of 2 or more trips.
+            // If either type has only Object parameters, we know we can convert.
+            assert(canConvertParameters(srcTypes, dstTypes));
+            return true;
+        }
+        return canConvertParameters(srcTypes, dstTypes);
+    }
+
+    /** Returns true if MHs.explicitCastArguments produces the same result as MH.asType.
+     *  If the type conversion is impossible for either, the result should be false.
+     */
+    /*non-public*/
+    boolean explicitCastEquivalentToAsType(MethodType newType) {
+        if (this == newType)  return true;
+        if (!explicitCastEquivalentToAsType(rtype, newType.rtype)) {
+            return false;
+        }
+        Class<?>[] srcTypes = newType.ptypes;
+        Class<?>[] dstTypes = ptypes;
+        if (dstTypes == srcTypes) {
+            return true;
+        }
+        assert(dstTypes.length == srcTypes.length);
+        for (int i = 0; i < dstTypes.length; i++) {
+            if (!explicitCastEquivalentToAsType(srcTypes[i], dstTypes[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /** Reports true if the src can be converted to the dst, by both asType and MHs.eCE,
+     *  and with the same effect.
+     *  MHs.eCA has the following "upgrades" to MH.asType:
+     *  1. interfaces are unchecked (that is, treated as if aliased to Object)
+     *     Therefore, {@code Object->CharSequence} is possible in both cases but has different semantics
+     *  2a. the full matrix of primitive-to-primitive conversions is supported
+     *      Narrowing like {@code long->byte} and basic-typing like {@code boolean->int}
+     *      are not supported by asType, but anything supported by asType is equivalent
+     *      with MHs.eCE.
+     *  2b. conversion of void->primitive means explicit cast has to insert zero/false/null.
+     *  3a. unboxing conversions can be followed by the full matrix of primitive conversions
+     *  3b. unboxing of null is permitted (creates a zero primitive value)
+     * Other than interfaces, reference-to-reference conversions are the same.
+     * Boxing primitives to references is the same for both operators.
+     */
+    private static boolean explicitCastEquivalentToAsType(Class<?> src, Class<?> dst) {
+        if (src == dst || dst == Object.class || dst == void.class) {
+            return true;
+        } else if (src.isPrimitive() && src != void.class) {
+            // Could be a prim/prim conversion, where casting is a strict superset.
+            // Or a boxing conversion, which is always to an exact wrapper class.
+            return canConvert(src, dst);
+        } else if (dst.isPrimitive()) {
+            // Unboxing behavior is different between MHs.eCA & MH.asType (see 3b).
+            return false;
+        } else {
+            // R->R always works, but we have to avoid a check-cast to an interface.
+            return !dst.isInterface() || dst.isAssignableFrom(src);
+        }
+    }
+
+    private boolean canConvertParameters(Class<?>[] srcTypes, Class<?>[] dstTypes) {
+        for (int i = 0; i < srcTypes.length; i++) {
+            if (!canConvert(srcTypes[i], dstTypes[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /*non-public*/
+    static boolean canConvert(Class<?> src, Class<?> dst) {
+        // short-circuit a few cases:
+        if (src == dst || src == Object.class || dst == Object.class)  return true;
+        // the remainder of this logic is documented in MethodHandle.asType
+        if (src.isPrimitive()) {
+            // can force void to an explicit null, a la reflect.Method.invoke
+            // can also force void to a primitive zero, by analogy
+            if (src == void.class)  return true;  //or !dst.isPrimitive()?
+            Wrapper sw = Wrapper.forPrimitiveType(src);
+            if (dst.isPrimitive()) {
+                // P->P must widen
+                return Wrapper.forPrimitiveType(dst).isConvertibleFrom(sw);
+            } else {
+                // P->R must box and widen
+                return dst.isAssignableFrom(sw.wrapperType());
+            }
+        } else if (dst.isPrimitive()) {
+            // any value can be dropped
+            if (dst == void.class)  return true;
+            Wrapper dw = Wrapper.forPrimitiveType(dst);
+            // R->P must be able to unbox (from a dynamically chosen type) and widen
+            // For example:
+            //   Byte/Number/Comparable/Object -> dw:Byte -> byte.
+            //   Character/Comparable/Object -> dw:Character -> char
+            //   Boolean/Comparable/Object -> dw:Boolean -> boolean
+            // This means that dw must be cast-compatible with src.
+            if (src.isAssignableFrom(dw.wrapperType())) {
+                return true;
+            }
+            // The above does not work if the source reference is strongly typed
+            // to a wrapper whose primitive must be widened.  For example:
+            //   Byte -> unbox:byte -> short/int/long/float/double
+            //   Character -> unbox:char -> int/long/float/double
+            if (Wrapper.isWrapperType(src) &&
+                dw.isConvertibleFrom(Wrapper.forWrapperType(src))) {
+                // can unbox from src and then widen to dst
+                return true;
+            }
+            // We have already covered cases which arise due to runtime unboxing
+            // of a reference type which covers several wrapper types:
+            //   Object -> cast:Integer -> unbox:int -> long/float/double
+            //   Serializable -> cast:Byte -> unbox:byte -> byte/short/int/long/float/double
+            // An marginal case is Number -> dw:Character -> char, which would be OK if there were a
+            // subclass of Number which wraps a value that can convert to char.
+            // Since there is none, we don't need an extra check here to cover char or boolean.
+            return false;
+        } else {
+            // R->R always works, since null is always valid dynamically
+            return true;
+        }
+    }
+
+    /** Reports the number of JVM stack slots required to invoke a method
+     * of this type.  Note that (for historical reasons) the JVM requires
+     * a second stack slot to pass long and double arguments.
+     * So this method returns {@link #parameterCount() parameterCount} plus the
+     * number of long and double parameters (if any).
+     * <p>
+     * This method is included for the benefit of applications that must
+     * generate bytecodes that process method handles and invokedynamic.
+     * @return the number of JVM stack slots for this type's parameters
+     */
+    /*non-public*/ int parameterSlotCount() {
+        return form.parameterSlotCount();
+    }
+
+    /// Queries which have to do with the bytecode architecture
+
+    // Android-changed: These methods aren't needed on Android and are unused within the JDK.
+    //
+    // int parameterSlotDepth(int num);
+    // int returnSlotCount();
+    //
+    // Android-changed: Removed cache of higher order adapters.
+    //
+    // Invokers invokers();
+
+    /**
+     * Finds or creates an instance of a method type, given the spelling of its bytecode descriptor.
+     * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[]) methodType}.
+     * Any class or interface name embedded in the descriptor string
+     * will be resolved by calling {@link ClassLoader#loadClass(java.lang.String)}
+     * on the given loader (or if it is null, on the system class loader).
+     * <p>
+     * Note that it is possible to encounter method types which cannot be
+     * constructed by this method, because their component types are
+     * not all reachable from a common class loader.
+     * <p>
+     * This method is included for the benefit of applications that must
+     * generate bytecodes that process method handles and {@code invokedynamic}.
+     * @param descriptor a bytecode-level type descriptor string "(T...)T"
+     * @param loader the class loader in which to look up the types
+     * @return a method type matching the bytecode-level type descriptor
+     * @throws NullPointerException if the string is null
+     * @throws IllegalArgumentException if the string is not well-formed
+     * @throws TypeNotPresentException if a named type cannot be found
+     */
     public static MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader)
-        throws IllegalArgumentException, TypeNotPresentException { return null; }
+        throws IllegalArgumentException, TypeNotPresentException
+    {
+        if (!descriptor.startsWith("(") ||  // also generates NPE if needed
+            descriptor.indexOf(')') < 0 ||
+            descriptor.indexOf('.') >= 0)
+            throw newIllegalArgumentException("not a method descriptor: "+descriptor);
+        List<Class<?>> types = BytecodeDescriptor.parseMethod(descriptor, loader);
+        Class<?> rtype = types.remove(types.size() - 1);
+        checkSlotCount(types.size());
+        Class<?>[] ptypes = listToArray(types);
+        return makeImpl(rtype, ptypes, true);
+    }
 
-    public String toMethodDescriptorString() { return null; }
+    /**
+     * Produces a bytecode descriptor representation of the method type.
+     * <p>
+     * Note that this is not a strict inverse of {@link #fromMethodDescriptorString fromMethodDescriptorString}.
+     * Two distinct classes which share a common name but have different class loaders
+     * will appear identical when viewed within descriptor strings.
+     * <p>
+     * This method is included for the benefit of applications that must
+     * generate bytecodes that process method handles and {@code invokedynamic}.
+     * {@link #fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader) fromMethodDescriptorString},
+     * because the latter requires a suitable class loader argument.
+     * @return the bytecode type descriptor representation
+     */
+    public String toMethodDescriptorString() {
+        String desc = methodDescriptor;
+        if (desc == null) {
+            desc = BytecodeDescriptor.unparse(this);
+            methodDescriptor = desc;
+        }
+        return desc;
+    }
+
+    /*non-public*/ static String toFieldDescriptorString(Class<?> cls) {
+        return BytecodeDescriptor.unparse(cls);
+    }
+
+    /// Serialization.
+
+    /**
+     * There are no serializable fields for {@code MethodType}.
+     */
+    private static final java.io.ObjectStreamField[] serialPersistentFields = { };
+
+    /**
+     * Save the {@code MethodType} instance to a stream.
+     *
+     * @serialData
+     * For portability, the serialized format does not refer to named fields.
+     * Instead, the return type and parameter type arrays are written directly
+     * from the {@code writeObject} method, using two calls to {@code s.writeObject}
+     * as follows:
+     * <blockquote><pre>{@code
+s.writeObject(this.returnType());
+s.writeObject(this.parameterArray());
+     * }</pre></blockquote>
+     * <p>
+     * The deserialized field values are checked as if they were
+     * provided to the factory method {@link #methodType(Class,Class[]) methodType}.
+     * For example, null values, or {@code void} parameter types,
+     * will lead to exceptions during deserialization.
+     * @param s the stream to write the object to
+     * @throws java.io.IOException if there is a problem writing the object
+     */
+    private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
+        s.defaultWriteObject();  // requires serialPersistentFields to be an empty array
+        s.writeObject(returnType());
+        s.writeObject(parameterArray());
+    }
+
+    /**
+     * Reconstitute the {@code MethodType} instance from a stream (that is,
+     * deserialize it).
+     * This instance is a scratch object with bogus final fields.
+     * It provides the parameters to the factory method called by
+     * {@link #readResolve readResolve}.
+     * After that call it is discarded.
+     * @param s the stream to read the object from
+     * @throws java.io.IOException if there is a problem reading the object
+     * @throws ClassNotFoundException if one of the component classes cannot be resolved
+     * @see #MethodType()
+     * @see #readResolve
+     * @see #writeObject
+     */
+    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
+        s.defaultReadObject();  // requires serialPersistentFields to be an empty array
+
+        Class<?>   returnType     = (Class<?>)   s.readObject();
+        Class<?>[] parameterArray = (Class<?>[]) s.readObject();
+
+        // Probably this object will never escape, but let's check
+        // the field values now, just to be sure.
+        checkRtype(returnType);
+        checkPtypes(parameterArray);
+
+        parameterArray = parameterArray.clone();  // make sure it is unshared
+        MethodType_init(returnType, parameterArray);
+    }
+
+    /**
+     * For serialization only.
+     * Sets the final fields to null, pending {@code Unsafe.putObject}.
+     */
+    private MethodType() {
+        this.rtype = null;
+        this.ptypes = null;
+    }
+    private void MethodType_init(Class<?> rtype, Class<?>[] ptypes) {
+        // In order to communicate these values to readResolve, we must
+        // store them into the implementation-specific final fields.
+        checkRtype(rtype);
+        checkPtypes(ptypes);
+        UNSAFE.putObject(this, rtypeOffset, rtype);
+        UNSAFE.putObject(this, ptypesOffset, ptypes);
+    }
+
+    // Support for resetting final fields while deserializing
+    private static final long rtypeOffset, ptypesOffset;
+    static {
+        try {
+            rtypeOffset = UNSAFE.objectFieldOffset
+                (MethodType.class.getDeclaredField("rtype"));
+            ptypesOffset = UNSAFE.objectFieldOffset
+                (MethodType.class.getDeclaredField("ptypes"));
+        } catch (Exception ex) {
+            throw new Error(ex);
+        }
+    }
+
+    /**
+     * Resolves and initializes a {@code MethodType} object
+     * after serialization.
+     * @return the fully initialized {@code MethodType} object
+     */
+    private Object readResolve() {
+        // Do not use a trusted path for deserialization:
+        //return makeImpl(rtype, ptypes, true);
+        // Verify all operands, and make sure ptypes is unshared:
+        return methodType(rtype, ptypes);
+    }
+
+    /**
+     * Simple implementation of weak concurrent intern set.
+     *
+     * @param <T> interned type
+     */
+    private static class ConcurrentWeakInternSet<T> {
+
+        private final ConcurrentMap<WeakEntry<T>, WeakEntry<T>> map;
+        private final ReferenceQueue<T> stale;
+
+        public ConcurrentWeakInternSet() {
+            this.map = new ConcurrentHashMap<>();
+            this.stale = new ReferenceQueue<>();
+        }
+
+        /**
+         * Get the existing interned element.
+         * This method returns null if no element is interned.
+         *
+         * @param elem element to look up
+         * @return the interned element
+         */
+        public T get(T elem) {
+            if (elem == null) throw new NullPointerException();
+            expungeStaleElements();
+
+            WeakEntry<T> value = map.get(new WeakEntry<>(elem));
+            if (value != null) {
+                T res = value.get();
+                if (res != null) {
+                    return res;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Interns the element.
+         * Always returns non-null element, matching the one in the intern set.
+         * Under the race against another add(), it can return <i>different</i>
+         * element, if another thread beats us to interning it.
+         *
+         * @param elem element to add
+         * @return element that was actually added
+         */
+        public T add(T elem) {
+            if (elem == null) throw new NullPointerException();
+
+            // Playing double race here, and so spinloop is required.
+            // First race is with two concurrent updaters.
+            // Second race is with GC purging weak ref under our feet.
+            // Hopefully, we almost always end up with a single pass.
+            T interned;
+            WeakEntry<T> e = new WeakEntry<>(elem, stale);
+            do {
+                expungeStaleElements();
+                WeakEntry<T> exist = map.putIfAbsent(e, e);
+                interned = (exist == null) ? elem : exist.get();
+            } while (interned == null);
+            return interned;
+        }
+
+        private void expungeStaleElements() {
+            Reference<? extends T> reference;
+            while ((reference = stale.poll()) != null) {
+                map.remove(reference);
+            }
+        }
+
+        private static class WeakEntry<T> extends WeakReference<T> {
+
+            public final int hashcode;
+
+            public WeakEntry(T key, ReferenceQueue<T> queue) {
+                super(key, queue);
+                hashcode = key.hashCode();
+            }
+
+            public WeakEntry(T key) {
+                super(key);
+                hashcode = key.hashCode();
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (obj instanceof WeakEntry) {
+                    Object that = ((WeakEntry) obj).get();
+                    Object mine = get();
+                    return (that == null || mine == null) ? (this == obj) : mine.equals(that);
+                }
+                return false;
+            }
+
+            @Override
+            public int hashCode() {
+                return hashcode;
+            }
+
+        }
+    }
 
 }
diff --git a/java/lang/invoke/VarHandle.java b/java/lang/invoke/VarHandle.java
index 562efb6..da56e8d 100644
--- a/java/lang/invoke/VarHandle.java
+++ b/java/lang/invoke/VarHandle.java
@@ -32,6 +32,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * A VarHandle is a dynamically strongly typed reference to a variable, or to a
@@ -450,8 +451,12 @@
     /** The target type for accesses. */
     private final Class<?> varType;
 
-    /** The coordinate types of a VarHandle instance. */
-    private final List<Class<?>> coordinateTypes;
+    /** This VarHandle's first coordinate, or null if this VarHandle has no coordinates. */
+    private final Class<?> coordinateType0;
+
+    /** This VarHandle's second coordinate, or null if this VarHandle has less than two
+     * coordinates. */
+    private final Class<?> coordinateType1;
 
     /** BitMask of supported access mode indexed by AccessMode.ordinal(). */
     private final int accessModesBitMask;
@@ -1932,8 +1937,14 @@
         // Android-removed: existing implementation.
         // MethodType typeGet = accessModeType(AccessMode.GET);
         // return typeGet.parameterList();
-        // Android-added: return instance field.
-        return coordinateTypes;
+        // Android-added: Android specific implementation.
+        if (coordinateType0 == null) {
+            return Collections.EMPTY_LIST;
+        } else if (coordinateType1 == null) {
+            return Collections.singletonList(coordinateType0);
+        } else {
+            return Collections.unmodifiableList(Arrays.asList(coordinateType0, coordinateType1));
+        }
     }
 
     /**
@@ -1964,16 +1975,12 @@
         // END Android-removed: Relies on internal class that is not part of the
         // Android implementation.
         // Android-added: alternative implementation.
-        switch (coordinateTypes.size()) {
-            case 0:
-                return accessMode.at.accessModeType(null, varType);
-            case 1:
-                return accessMode.at.accessModeType(coordinateTypes.get(0), varType);
-            case 2:
-                return accessMode.at.accessModeType(coordinateTypes.get(0), varType,
-                                                    coordinateTypes.get(1));
-            default:
-                throw new InternalError("bad coordinateTypes: " + coordinateTypes);
+        if (coordinateType1 == null) {
+            // accessModeType() treats the first argument as the
+            // receiver and adapts accordingly if it is null.
+            return accessMode.at.accessModeType(coordinateType0, varType);
+        } else {
+            return accessMode.at.accessModeType(coordinateType0, varType, coordinateType1);
         }
     }
 
@@ -2201,8 +2208,9 @@
      * @hide
      */
     VarHandle(Class<?> varType, boolean isFinal) {
-        this.varType = varType;
-        this.coordinateTypes = Collections.EMPTY_LIST;
+        this.varType = Objects.requireNonNull(varType);
+        this.coordinateType0 = null;
+        this.coordinateType1 = null;
         this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
     }
 
@@ -2211,12 +2219,13 @@
      *
      * @param varType the variable type of variables to be referenced
      * @param isFinal  whether the target variables are final (non-modifiable)
-     * @param coordinate the coordinate
+     * @param coordinateType the coordinate
      * @hide
      */
-    VarHandle(Class<?> varType, boolean isFinal, Class<?> coordinate) {
-        this.varType = varType;
-        this.coordinateTypes = Collections.singletonList(coordinate);
+    VarHandle(Class<?> varType, boolean isFinal, Class<?> coordinateType) {
+        this.varType = Objects.requireNonNull(varType);
+        this.coordinateType0 = Objects.requireNonNull(coordinateType);
+        this.coordinateType1 = null;
         this.accessModesBitMask = alignedAccessModesBitMask(varType, isFinal);
     }
 
@@ -2226,15 +2235,16 @@
      * @param varType the variable type of variables to be referenced
      * @param backingArrayType the type of the array accesses will be performed on
      * @param isFinal whether the target variables are final (non-modifiable)
-     * @param coordinate0 the first coordinate
-     * @param coordinate1 the second coordinate
+     * @param coordinateType0 the first coordinate
+     * @param coordinateType1 the second coordinate
      * @hide
      */
     VarHandle(Class<?> varType, Class<?> backingArrayType,  boolean isFinal,
-              Class<?> coordinate0, Class<?> coordinate1) {
-        this.varType = varType;
-        this.coordinateTypes = Collections.unmodifiableList(
-            Arrays.asList(coordinate0, coordinate1));
+              Class<?> coordinateType0, Class<?> coordinateType1) {
+        this.varType = Objects.requireNonNull(varType);
+        this.coordinateType0 = Objects.requireNonNull(coordinateType0);
+        this.coordinateType1 = Objects.requireNonNull(coordinateType1);
+        Objects.requireNonNull(backingArrayType);
         Class<?> backingArrayComponentType = backingArrayType.getComponentType();
         if (backingArrayComponentType != varType && backingArrayComponentType != byte.class) {
             throw new InternalError("Unsupported backingArrayType: " + backingArrayType);
diff --git a/java/net/URL.java b/java/net/URL.java
index d4ed35b..576439d 100644
--- a/java/net/URL.java
+++ b/java/net/URL.java
@@ -1202,10 +1202,10 @@
                         handler = new sun.net.www.protocol.jar.Handler();
                     } else if (protocol.equals("http")) {
                         handler = (URLStreamHandler)Class.
-                            forName("libcore.net.http.HttpHandler").newInstance();
+                            forName("com.android.okhttp.HttpHandler").newInstance();
                     } else if (protocol.equals("https")) {
                         handler = (URLStreamHandler)Class.
-                            forName("libcore.net.http.HttpsHandler").newInstance();
+                            forName("com.android.okhttp.HttpsHandler").newInstance();
                     }
                     // END Android-changed
                 } catch (Exception e) {
diff --git a/java/text/DecimalFormat.java b/java/text/DecimalFormat.java
index d2e8530..ff9d90f 100644
--- a/java/text/DecimalFormat.java
+++ b/java/text/DecimalFormat.java
@@ -387,7 +387,8 @@
     // to implement DecimalFormat.
 
     // Android-added: ICU DecimalFormat to delegate to.
-    private transient android.icu.text.DecimalFormat icuDecimalFormat;
+    // TODO(b/68143370): switch back to ICU DecimalFormat once it can reproduce ICU 58 behavior.
+    private transient android.icu.text.DecimalFormat_ICU58_Android icuDecimalFormat;
 
     /**
      * Creates a DecimalFormat using the default pattern and symbols
@@ -486,7 +487,7 @@
      * {@link #icuDecimalFormat} in the process. This should only be called from constructors.
      */
     private void initPattern(String pattern) {
-        this.icuDecimalFormat =  new android.icu.text.DecimalFormat(pattern,
+        this.icuDecimalFormat =  new android.icu.text.DecimalFormat_ICU58_Android(pattern,
                 symbols.getIcuDecimalFormatSymbols());
         updateFieldsFromIcu();
     }
@@ -1186,7 +1187,7 @@
         // BEGIN Android-changed: Use ICU, remove fast path related code.
         try {
             DecimalFormat other = (DecimalFormat) super.clone();
-            other.icuDecimalFormat = (android.icu.text.DecimalFormat) icuDecimalFormat.clone();
+            other.icuDecimalFormat = (android.icu.text.DecimalFormat_ICU58_Android) icuDecimalFormat.clone();
             other.symbols = (DecimalFormatSymbols) symbols.clone();
             return other;
         } catch (Exception e) {
@@ -1216,7 +1217,7 @@
             && compareIcuRoundingIncrement(other.icuDecimalFormat);
     }
 
-    private boolean compareIcuRoundingIncrement(android.icu.text.DecimalFormat other) {
+    private boolean compareIcuRoundingIncrement(android.icu.text.DecimalFormat_ICU58_Android other) {
         BigDecimal increment = this.icuDecimalFormat.getRoundingIncrement();
         if (increment != null) {
             return (other.getRoundingIncrement() != null)
diff --git a/javax/obex/ObexHelper.java b/javax/obex/ObexHelper.java
index fa50943..478297f 100644
--- a/javax/obex/ObexHelper.java
+++ b/javax/obex/ObexHelper.java
@@ -80,6 +80,9 @@
     // The minimum allowed max packet size is 255 according to the OBEX specification
     public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
 
+    // The length of OBEX Byte Sequency Header Id according to the OBEX specification
+    public static final int OBEX_BYTE_SEQ_HEADER_LEN = 0x03;
+
     /**
      * Temporary workaround to be able to push files to Windows 7.
      * TODO: Should be removed as soon as Microsoft updates their driver.
@@ -205,12 +208,15 @@
                     case 0x40:
                         boolean trimTail = true;
                         index++;
-                        length = 0xFF & headerArray[index];
-                        length = length << 8;
-                        index++;
-                        length += 0xFF & headerArray[index];
-                        length -= 3;
-                        index++;
+                        length = ((0xFF & headerArray[index]) << 8) +
+                                 (0xFF & headerArray[index + 1]);
+                        index += 2;
+                        if (length <= OBEX_BYTE_SEQ_HEADER_LEN) {
+                            Log.e(TAG, "Remote sent an OBEX packet with " +
+                                  "incorrect header length = " + length);
+                            break;
+                        }
+                        length -= OBEX_BYTE_SEQ_HEADER_LEN;
                         value = new byte[length];
                         System.arraycopy(headerArray, index, value, 0, length);
                         if (length == 0 || (length > 0 && (value[length - 1] != 0))) {
diff --git a/org/json/JSONArrayTest.java b/org/json/JSONArrayTest.java
deleted file mode 100644
index 4c86b8a..0000000
--- a/org/json/JSONArrayTest.java
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open 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 org.json;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import junit.framework.TestCase;
-
-/**
- * This black box test was written without inspecting the non-free org.json sourcecode.
- */
-public class JSONArrayTest extends TestCase {
-
-    public void testEmptyArray() throws JSONException {
-        JSONArray array = new JSONArray();
-        assertEquals(0, array.length());
-        assertEquals("", array.join(" AND "));
-        try {
-            array.get(0);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            array.getBoolean(0);
-            fail();
-        } catch (JSONException e) {
-        }
-
-        assertEquals("[]", array.toString());
-        assertEquals("[]", array.toString(4));
-
-        // out of bounds is co-opted with defaulting
-        assertTrue(array.isNull(0));
-        assertNull(array.opt(0));
-        assertFalse(array.optBoolean(0));
-        assertTrue(array.optBoolean(0, true));
-
-        // bogus (but documented) behaviour: returns null rather than an empty object!
-        assertNull(array.toJSONObject(new JSONArray()));
-    }
-
-    public void testEqualsAndHashCode() throws JSONException {
-        JSONArray a = new JSONArray();
-        JSONArray b = new JSONArray();
-        assertTrue(a.equals(b));
-        assertEquals("equals() not consistent with hashCode()", a.hashCode(), b.hashCode());
-
-        a.put(true);
-        a.put(false);
-        b.put(true);
-        b.put(false);
-        assertTrue(a.equals(b));
-        assertEquals(a.hashCode(), b.hashCode());
-
-        b.put(true);
-        assertFalse(a.equals(b));
-        assertTrue(a.hashCode() != b.hashCode());
-    }
-
-    public void testBooleans() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put(true);
-        array.put(false);
-        array.put(2, false);
-        array.put(3, false);
-        array.put(2, true);
-        assertEquals("[true,false,true,false]", array.toString());
-        assertEquals(4, array.length());
-        assertEquals(Boolean.TRUE, array.get(0));
-        assertEquals(Boolean.FALSE, array.get(1));
-        assertEquals(Boolean.TRUE, array.get(2));
-        assertEquals(Boolean.FALSE, array.get(3));
-        assertFalse(array.isNull(0));
-        assertFalse(array.isNull(1));
-        assertFalse(array.isNull(2));
-        assertFalse(array.isNull(3));
-        assertEquals(true, array.optBoolean(0));
-        assertEquals(false, array.optBoolean(1, true));
-        assertEquals(true, array.optBoolean(2, false));
-        assertEquals(false, array.optBoolean(3));
-        assertEquals("true", array.getString(0));
-        assertEquals("false", array.getString(1));
-        assertEquals("true", array.optString(2));
-        assertEquals("false", array.optString(3, "x"));
-        assertEquals("[\n     true,\n     false,\n     true,\n     false\n]", array.toString(5));
-
-        JSONArray other = new JSONArray();
-        other.put(true);
-        other.put(false);
-        other.put(true);
-        other.put(false);
-        assertTrue(array.equals(other));
-        other.put(true);
-        assertFalse(array.equals(other));
-
-        other = new JSONArray();
-        other.put("true");
-        other.put("false");
-        other.put("truE");
-        other.put("FALSE");
-        assertFalse(array.equals(other));
-        assertFalse(other.equals(array));
-        assertEquals(true, other.getBoolean(0));
-        assertEquals(false, other.optBoolean(1, true));
-        assertEquals(true, other.optBoolean(2));
-        assertEquals(false, other.getBoolean(3));
-    }
-
-    // http://code.google.com/p/android/issues/detail?id=16411
-    public void testCoerceStringToBoolean() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put("maybe");
-        try {
-            array.getBoolean(0);
-            fail();
-        } catch (JSONException expected) {
-        }
-        assertEquals(false, array.optBoolean(0));
-        assertEquals(true, array.optBoolean(0, true));
-    }
-
-    public void testNulls() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put(3, (Collection) null);
-        array.put(0, JSONObject.NULL);
-        assertEquals(4, array.length());
-        assertEquals("[null,null,null,null]", array.toString());
-
-        // there's 2 ways to represent null; each behaves differently!
-        assertEquals(JSONObject.NULL, array.get(0));
-        try {
-            array.get(1);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            array.get(2);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            array.get(3);
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals(JSONObject.NULL, array.opt(0));
-        assertEquals(null, array.opt(1));
-        assertEquals(null, array.opt(2));
-        assertEquals(null, array.opt(3));
-        assertTrue(array.isNull(0));
-        assertTrue(array.isNull(1));
-        assertTrue(array.isNull(2));
-        assertTrue(array.isNull(3));
-        assertEquals("null", array.optString(0));
-        assertEquals("", array.optString(1));
-        assertEquals("", array.optString(2));
-        assertEquals("", array.optString(3));
-    }
-
-    /**
-     * Our behaviour is questioned by this bug:
-     * http://code.google.com/p/android/issues/detail?id=7257
-     */
-    public void testParseNullYieldsJSONObjectNull() throws JSONException {
-        JSONArray array = new JSONArray("[\"null\",null]");
-        array.put((Collection) null);
-        assertEquals("null", array.get(0));
-        assertEquals(JSONObject.NULL, array.get(1));
-        try {
-            array.get(2);
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals("null", array.getString(0));
-        assertEquals("null", array.getString(1));
-        try {
-            array.getString(2);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testNumbers() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put(Double.MIN_VALUE);
-        array.put(9223372036854775806L);
-        array.put(Double.MAX_VALUE);
-        array.put(-0d);
-        assertEquals(4, array.length());
-
-        // toString() and getString(int) return different values for -0d
-        assertEquals("[4.9E-324,9223372036854775806,1.7976931348623157E308,-0]", array.toString());
-
-        assertEquals(Double.MIN_VALUE, array.get(0));
-        assertEquals(9223372036854775806L, array.get(1));
-        assertEquals(Double.MAX_VALUE, array.get(2));
-        assertEquals(-0d, array.get(3));
-        assertEquals(Double.MIN_VALUE, array.getDouble(0));
-        assertEquals(9.223372036854776E18, array.getDouble(1));
-        assertEquals(Double.MAX_VALUE, array.getDouble(2));
-        assertEquals(-0d, array.getDouble(3));
-        assertEquals(0, array.getLong(0));
-        assertEquals(9223372036854775806L, array.getLong(1));
-        assertEquals(Long.MAX_VALUE, array.getLong(2));
-        assertEquals(0, array.getLong(3));
-        assertEquals(0, array.getInt(0));
-        assertEquals(-2, array.getInt(1));
-        assertEquals(Integer.MAX_VALUE, array.getInt(2));
-        assertEquals(0, array.getInt(3));
-        assertEquals(Double.MIN_VALUE, array.opt(0));
-        assertEquals(Double.MIN_VALUE, array.optDouble(0));
-        assertEquals(0, array.optLong(0, 1L));
-        assertEquals(0, array.optInt(0, 1));
-        assertEquals("4.9E-324", array.getString(0));
-        assertEquals("9223372036854775806", array.getString(1));
-        assertEquals("1.7976931348623157E308", array.getString(2));
-        assertEquals("-0.0", array.getString(3));
-
-        JSONArray other = new JSONArray();
-        other.put(Double.MIN_VALUE);
-        other.put(9223372036854775806L);
-        other.put(Double.MAX_VALUE);
-        other.put(-0d);
-        assertTrue(array.equals(other));
-        other.put(0, 0L);
-        assertFalse(array.equals(other));
-    }
-
-    public void testStrings() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put("true");
-        array.put("5.5");
-        array.put("9223372036854775806");
-        array.put("null");
-        array.put("5\"8' tall");
-        assertEquals(5, array.length());
-        assertEquals("[\"true\",\"5.5\",\"9223372036854775806\",\"null\",\"5\\\"8' tall\"]",
-                array.toString());
-
-        // although the documentation doesn't mention it, join() escapes text and wraps
-        // strings in quotes
-        assertEquals("\"true\" \"5.5\" \"9223372036854775806\" \"null\" \"5\\\"8' tall\"",
-                array.join(" "));
-
-        assertEquals("true", array.get(0));
-        assertEquals("null", array.getString(3));
-        assertEquals("5\"8' tall", array.getString(4));
-        assertEquals("true", array.opt(0));
-        assertEquals("5.5", array.optString(1));
-        assertEquals("9223372036854775806", array.optString(2, null));
-        assertEquals("null", array.optString(3, "-1"));
-        assertFalse(array.isNull(0));
-        assertFalse(array.isNull(3));
-
-        assertEquals(true, array.getBoolean(0));
-        assertEquals(true, array.optBoolean(0));
-        assertEquals(true, array.optBoolean(0, false));
-        assertEquals(0, array.optInt(0));
-        assertEquals(-2, array.optInt(0, -2));
-
-        assertEquals(5.5d, array.getDouble(1));
-        assertEquals(5L, array.getLong(1));
-        assertEquals(5, array.getInt(1));
-        assertEquals(5, array.optInt(1, 3));
-
-        // The last digit of the string is a 6 but getLong returns a 7. It's probably parsing as a
-        // double and then converting that to a long. This is consistent with JavaScript.
-        assertEquals(9223372036854775807L, array.getLong(2));
-        assertEquals(9.223372036854776E18, array.getDouble(2));
-        assertEquals(Integer.MAX_VALUE, array.getInt(2));
-
-        assertFalse(array.isNull(3));
-        try {
-            array.getDouble(3);
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals(Double.NaN, array.optDouble(3));
-        assertEquals(-1.0d, array.optDouble(3, -1.0d));
-    }
-
-    public void testJoin() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put((Collection) null);
-        assertEquals("null", array.join(" & "));
-        array.put("\"");
-        assertEquals("null & \"\\\"\"", array.join(" & "));
-        array.put(5);
-        assertEquals("null & \"\\\"\" & 5", array.join(" & "));
-        array.put(true);
-        assertEquals("null & \"\\\"\" & 5 & true", array.join(" & "));
-        array.put(new JSONArray(Arrays.asList(true, false)));
-        assertEquals("null & \"\\\"\" & 5 & true & [true,false]", array.join(" & "));
-        array.put(new JSONObject(Collections.singletonMap("x", 6)));
-        assertEquals("null & \"\\\"\" & 5 & true & [true,false] & {\"x\":6}", array.join(" & "));
-    }
-
-    public void testJoinWithNull() throws JSONException {
-        JSONArray array = new JSONArray(Arrays.asList(5, 6));
-        assertEquals("5null6", array.join(null));
-    }
-
-    public void testJoinWithSpecialCharacters() throws JSONException {
-        JSONArray array = new JSONArray(Arrays.asList(5, 6));
-        assertEquals("5\"6", array.join("\""));
-    }
-
-    public void testToJSONObject() throws JSONException {
-        JSONArray keys = new JSONArray();
-        keys.put("a");
-        keys.put("b");
-
-        JSONArray values = new JSONArray();
-        values.put(5.5d);
-        values.put(false);
-
-        JSONObject object = values.toJSONObject(keys);
-        assertEquals(5.5d, object.get("a"));
-        assertEquals(false, object.get("b"));
-
-        keys.put(0, "a");
-        values.put(0, 11.0d);
-        assertEquals(5.5d, object.get("a"));
-    }
-
-    public void testToJSONObjectWithNulls() throws JSONException {
-        JSONArray keys = new JSONArray();
-        keys.put("a");
-        keys.put("b");
-
-        JSONArray values = new JSONArray();
-        values.put(5.5d);
-        values.put((Collection) null);
-
-        // null values are stripped!
-        JSONObject object = values.toJSONObject(keys);
-        assertEquals(1, object.length());
-        assertFalse(object.has("b"));
-        assertEquals("{\"a\":5.5}", object.toString());
-    }
-
-    public void testToJSONObjectMoreNamesThanValues() throws JSONException {
-        JSONArray keys = new JSONArray();
-        keys.put("a");
-        keys.put("b");
-        JSONArray values = new JSONArray();
-        values.put(5.5d);
-        JSONObject object = values.toJSONObject(keys);
-        assertEquals(1, object.length());
-        assertEquals(5.5d, object.get("a"));
-    }
-
-    public void testToJSONObjectMoreValuesThanNames() throws JSONException {
-        JSONArray keys = new JSONArray();
-        keys.put("a");
-        JSONArray values = new JSONArray();
-        values.put(5.5d);
-        values.put(11.0d);
-        JSONObject object = values.toJSONObject(keys);
-        assertEquals(1, object.length());
-        assertEquals(5.5d, object.get("a"));
-    }
-
-    public void testToJSONObjectNullKey() throws JSONException {
-        JSONArray keys = new JSONArray();
-        keys.put(JSONObject.NULL);
-        JSONArray values = new JSONArray();
-        values.put(5.5d);
-        JSONObject object = values.toJSONObject(keys);
-        assertEquals(1, object.length());
-        assertEquals(5.5d, object.get("null"));
-    }
-
-    public void testPutUnsupportedNumbers() throws JSONException {
-        JSONArray array = new JSONArray();
-
-        try {
-            array.put(Double.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            array.put(0, Double.NEGATIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            array.put(0, Double.POSITIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testPutUnsupportedNumbersAsObject() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put(Double.valueOf(Double.NaN));
-        array.put(Double.valueOf(Double.NEGATIVE_INFINITY));
-        array.put(Double.valueOf(Double.POSITIVE_INFINITY));
-        assertEquals(null, array.toString());
-    }
-
-    /**
-     * Although JSONArray is usually defensive about which numbers it accepts,
-     * it doesn't check inputs in its constructor.
-     */
-    public void testCreateWithUnsupportedNumbers() throws JSONException {
-        JSONArray array = new JSONArray(Arrays.asList(5.5, Double.NaN));
-        assertEquals(2, array.length());
-        assertEquals(5.5, array.getDouble(0));
-        assertEquals(Double.NaN, array.getDouble(1));
-    }
-
-    public void testToStringWithUnsupportedNumbers() throws JSONException {
-        // when the array contains an unsupported number, toString returns null!
-        JSONArray array = new JSONArray(Arrays.asList(5.5, Double.NaN));
-        assertNull(array.toString());
-    }
-
-    public void testListConstructorCopiesContents() throws JSONException {
-        List<Object> contents = Arrays.<Object>asList(5);
-        JSONArray array = new JSONArray(contents);
-        contents.set(0, 10);
-        assertEquals(5, array.get(0));
-    }
-
-    public void testTokenerConstructor() throws JSONException {
-        JSONArray object = new JSONArray(new JSONTokener("[false]"));
-        assertEquals(1, object.length());
-        assertEquals(false, object.get(0));
-    }
-
-    public void testTokenerConstructorWrongType() throws JSONException {
-        try {
-            new JSONArray(new JSONTokener("{\"foo\": false}"));
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testTokenerConstructorNull() throws JSONException {
-        try {
-            new JSONArray((JSONTokener) null);
-            fail();
-        } catch (NullPointerException e) {
-        }
-    }
-
-    public void testTokenerConstructorParseFail() {
-        try {
-            new JSONArray(new JSONTokener("["));
-            fail();
-        } catch (JSONException e) {
-        } catch (StackOverflowError e) {
-            fail("Stack overflowed on input: \"[\"");
-        }
-    }
-
-    public void testStringConstructor() throws JSONException {
-        JSONArray object = new JSONArray("[false]");
-        assertEquals(1, object.length());
-        assertEquals(false, object.get(0));
-    }
-
-    public void testStringConstructorWrongType() throws JSONException {
-        try {
-            new JSONArray("{\"foo\": false}");
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testStringConstructorNull() throws JSONException {
-        try {
-            new JSONArray((String) null);
-            fail();
-        } catch (NullPointerException e) {
-        }
-    }
-
-    public void testStringConstructorParseFail() {
-        try {
-            new JSONArray("[");
-            fail();
-        } catch (JSONException e) {
-        } catch (StackOverflowError e) {
-            fail("Stack overflowed on input: \"[\"");
-        }
-    }
-
-    public void testCreate() throws JSONException {
-        JSONArray array = new JSONArray(Arrays.asList(5.5, true));
-        assertEquals(2, array.length());
-        assertEquals(5.5, array.getDouble(0));
-        assertEquals(true, array.get(1));
-        assertEquals("[5.5,true]", array.toString());
-    }
-
-    public void testAccessOutOfBounds() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put("foo");
-        assertEquals(null, array.opt(3));
-        assertEquals(null, array.opt(-3));
-        assertEquals("", array.optString(3));
-        assertEquals("", array.optString(-3));
-        try {
-            array.get(3);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            array.get(-3);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            array.getString(3);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            array.getString(-3);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void test_remove() throws Exception {
-        JSONArray a = new JSONArray();
-        assertEquals(null, a.remove(-1));
-        assertEquals(null, a.remove(0));
-
-        a.put("hello");
-        assertEquals(null, a.remove(-1));
-        assertEquals(null, a.remove(1));
-        assertEquals("hello", a.remove(0));
-        assertEquals(null, a.remove(0));
-    }
-
-    enum MyEnum { A, B, C; }
-
-    // https://code.google.com/p/android/issues/detail?id=62539
-    public void testEnums() throws Exception {
-        // This works because it's in java.* and any class in there falls back to toString.
-        JSONArray a1 = new JSONArray(java.lang.annotation.RetentionPolicy.values());
-        assertEquals("[\"SOURCE\",\"CLASS\",\"RUNTIME\"]", a1.toString());
-
-        // This doesn't because it's not.
-        JSONArray a2 = new JSONArray(MyEnum.values());
-        assertEquals("[null,null,null]", a2.toString());
-    }
-}
diff --git a/org/json/JSONObjectTest.java b/org/json/JSONObjectTest.java
deleted file mode 100644
index 07d1cf6..0000000
--- a/org/json/JSONObjectTest.java
+++ /dev/null
@@ -1,1047 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open 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 org.json;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeMap;
-import junit.framework.TestCase;
-
-/**
- * This black box test was written without inspecting the non-free org.json sourcecode.
- */
-public class JSONObjectTest extends TestCase {
-
-    public void testEmptyObject() throws JSONException {
-        JSONObject object = new JSONObject();
-        assertEquals(0, object.length());
-
-        // bogus (but documented) behaviour: returns null rather than the empty object!
-        assertNull(object.names());
-
-        // returns null rather than an empty array!
-        assertNull(object.toJSONArray(new JSONArray()));
-        assertEquals("{}", object.toString());
-        assertEquals("{}", object.toString(5));
-        try {
-            object.get("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.getBoolean("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.getDouble("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.getInt("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.getJSONArray("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.getJSONObject("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.getLong("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.getString("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-        assertFalse(object.has("foo"));
-        assertTrue(object.isNull("foo")); // isNull also means "is not present"
-        assertNull(object.opt("foo"));
-        assertEquals(false, object.optBoolean("foo"));
-        assertEquals(true, object.optBoolean("foo", true));
-        assertEquals(Double.NaN, object.optDouble("foo"));
-        assertEquals(5.0, object.optDouble("foo", 5.0));
-        assertEquals(0, object.optInt("foo"));
-        assertEquals(5, object.optInt("foo", 5));
-        assertEquals(null, object.optJSONArray("foo"));
-        assertEquals(null, object.optJSONObject("foo"));
-        assertEquals(0, object.optLong("foo"));
-        assertEquals(Long.MAX_VALUE-1, object.optLong("foo", Long.MAX_VALUE-1));
-        assertEquals("", object.optString("foo")); // empty string is default!
-        assertEquals("bar", object.optString("foo", "bar"));
-        assertNull(object.remove("foo"));
-    }
-
-    public void testEqualsAndHashCode() throws JSONException {
-        JSONObject a = new JSONObject();
-        JSONObject b = new JSONObject();
-
-        // JSON object doesn't override either equals or hashCode (!)
-        assertFalse(a.equals(b));
-        assertEquals(a.hashCode(), System.identityHashCode(a));
-    }
-
-    public void testGet() throws JSONException {
-        JSONObject object = new JSONObject();
-        Object value = new Object();
-        object.put("foo", value);
-        object.put("bar", new Object());
-        object.put("baz", new Object());
-        assertSame(value, object.get("foo"));
-        try {
-            object.get("FOO");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.put(null, value);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.get(null);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testPut() throws JSONException {
-        JSONObject object = new JSONObject();
-        assertSame(object, object.put("foo", true));
-        object.put("foo", false);
-        assertEquals(false, object.get("foo"));
-
-        object.put("foo", 5.0d);
-        assertEquals(5.0d, object.get("foo"));
-        object.put("foo", 0);
-        assertEquals(0, object.get("foo"));
-        object.put("bar", Long.MAX_VALUE - 1);
-        assertEquals(Long.MAX_VALUE - 1, object.get("bar"));
-        object.put("baz", "x");
-        assertEquals("x", object.get("baz"));
-        object.put("bar", JSONObject.NULL);
-        assertSame(JSONObject.NULL, object.get("bar"));
-    }
-
-    public void testPutNullRemoves() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", "bar");
-        object.put("foo", (Collection) null);
-        assertEquals(0, object.length());
-        assertFalse(object.has("foo"));
-        try {
-            object.get("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testPutOpt() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", "bar");
-        object.putOpt("foo", null);
-        assertEquals("bar", object.get("foo"));
-        object.putOpt(null, null);
-        assertEquals(1, object.length());
-        object.putOpt(null, "bar");
-        assertEquals(1, object.length());
-    }
-
-    public void testPutOptUnsupportedNumbers() throws JSONException {
-        JSONObject object = new JSONObject();
-        try {
-            object.putOpt("foo", Double.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.putOpt("foo", Double.NEGATIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.putOpt("foo", Double.POSITIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testRemove() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", "bar");
-        assertEquals(null, object.remove(null));
-        assertEquals(null, object.remove(""));
-        assertEquals(null, object.remove("bar"));
-        assertEquals("bar", object.remove("foo"));
-        assertEquals(null, object.remove("foo"));
-    }
-
-    public void testBooleans() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", true);
-        object.put("bar", false);
-        object.put("baz", "true");
-        object.put("quux", "false");
-        assertEquals(4, object.length());
-        assertEquals(true, object.getBoolean("foo"));
-        assertEquals(false, object.getBoolean("bar"));
-        assertEquals(true, object.getBoolean("baz"));
-        assertEquals(false, object.getBoolean("quux"));
-        assertFalse(object.isNull("foo"));
-        assertFalse(object.isNull("quux"));
-        assertTrue(object.has("foo"));
-        assertTrue(object.has("quux"));
-        assertFalse(object.has("missing"));
-        assertEquals(true, object.optBoolean("foo"));
-        assertEquals(false, object.optBoolean("bar"));
-        assertEquals(true, object.optBoolean("baz"));
-        assertEquals(false, object.optBoolean("quux"));
-        assertEquals(false, object.optBoolean("missing"));
-        assertEquals(true, object.optBoolean("foo", true));
-        assertEquals(false, object.optBoolean("bar", true));
-        assertEquals(true, object.optBoolean("baz", true));
-        assertEquals(false, object.optBoolean("quux", true));
-        assertEquals(true, object.optBoolean("missing", true));
-
-        object.put("foo", "truE");
-        object.put("bar", "FALSE");
-        assertEquals(true, object.getBoolean("foo"));
-        assertEquals(false, object.getBoolean("bar"));
-        assertEquals(true, object.optBoolean("foo"));
-        assertEquals(false, object.optBoolean("bar"));
-        assertEquals(true, object.optBoolean("foo", false));
-        assertEquals(false, object.optBoolean("bar", false));
-    }
-
-    // http://code.google.com/p/android/issues/detail?id=16411
-    public void testCoerceStringToBoolean() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", "maybe");
-        try {
-            object.getBoolean("foo");
-            fail();
-        } catch (JSONException expected) {
-        }
-        assertEquals(false, object.optBoolean("foo"));
-        assertEquals(true, object.optBoolean("foo", true));
-    }
-
-    public void testNumbers() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", Double.MIN_VALUE);
-        object.put("bar", 9223372036854775806L);
-        object.put("baz", Double.MAX_VALUE);
-        object.put("quux", -0d);
-        assertEquals(4, object.length());
-
-        String toString = object.toString();
-        assertTrue(toString, toString.contains("\"foo\":4.9E-324"));
-        assertTrue(toString, toString.contains("\"bar\":9223372036854775806"));
-        assertTrue(toString, toString.contains("\"baz\":1.7976931348623157E308"));
-
-        // toString() and getString() return different values for -0d!
-        assertTrue(toString, toString.contains("\"quux\":-0}") // no trailing decimal point
-                || toString.contains("\"quux\":-0,"));
-
-        assertEquals(Double.MIN_VALUE, object.get("foo"));
-        assertEquals(9223372036854775806L, object.get("bar"));
-        assertEquals(Double.MAX_VALUE, object.get("baz"));
-        assertEquals(-0d, object.get("quux"));
-        assertEquals(Double.MIN_VALUE, object.getDouble("foo"));
-        assertEquals(9.223372036854776E18, object.getDouble("bar"));
-        assertEquals(Double.MAX_VALUE, object.getDouble("baz"));
-        assertEquals(-0d, object.getDouble("quux"));
-        assertEquals(0, object.getLong("foo"));
-        assertEquals(9223372036854775806L, object.getLong("bar"));
-        assertEquals(Long.MAX_VALUE, object.getLong("baz"));
-        assertEquals(0, object.getLong("quux"));
-        assertEquals(0, object.getInt("foo"));
-        assertEquals(-2, object.getInt("bar"));
-        assertEquals(Integer.MAX_VALUE, object.getInt("baz"));
-        assertEquals(0, object.getInt("quux"));
-        assertEquals(Double.MIN_VALUE, object.opt("foo"));
-        assertEquals(9223372036854775806L, object.optLong("bar"));
-        assertEquals(Double.MAX_VALUE, object.optDouble("baz"));
-        assertEquals(0, object.optInt("quux"));
-        assertEquals(Double.MIN_VALUE, object.opt("foo"));
-        assertEquals(9223372036854775806L, object.optLong("bar"));
-        assertEquals(Double.MAX_VALUE, object.optDouble("baz"));
-        assertEquals(0, object.optInt("quux"));
-        assertEquals(Double.MIN_VALUE, object.optDouble("foo", 5.0d));
-        assertEquals(9223372036854775806L, object.optLong("bar", 1L));
-        assertEquals(Long.MAX_VALUE, object.optLong("baz", 1L));
-        assertEquals(0, object.optInt("quux", -1));
-        assertEquals("4.9E-324", object.getString("foo"));
-        assertEquals("9223372036854775806", object.getString("bar"));
-        assertEquals("1.7976931348623157E308", object.getString("baz"));
-        assertEquals("-0.0", object.getString("quux"));
-    }
-
-    public void testFloats() throws JSONException {
-        JSONObject object = new JSONObject();
-        try {
-            object.put("foo", (Float) Float.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.put("foo", (Float) Float.NEGATIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.put("foo", (Float) Float.POSITIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testOtherNumbers() throws JSONException {
-        Number nan = new Number() {
-            public int intValue() {
-                throw new UnsupportedOperationException();
-            }
-            public long longValue() {
-                throw new UnsupportedOperationException();
-            }
-            public float floatValue() {
-                throw new UnsupportedOperationException();
-            }
-            public double doubleValue() {
-                return Double.NaN;
-            }
-            @Override public String toString() {
-                return "x";
-            }
-        };
-
-        JSONObject object = new JSONObject();
-        try {
-            object.put("foo", nan);
-            fail("Object.put() accepted a NaN (via a custom Number class)");
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testForeignObjects() throws JSONException {
-        Object foreign = new Object() {
-            @Override public String toString() {
-                return "x";
-            }
-        };
-
-        // foreign object types are accepted and treated as Strings!
-        JSONObject object = new JSONObject();
-        object.put("foo", foreign);
-        assertEquals("{\"foo\":\"x\"}", object.toString());
-    }
-
-    public void testNullKeys() {
-        try {
-            new JSONObject().put(null, false);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONObject().put(null, 0.0d);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONObject().put(null, 5);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONObject().put(null, 5L);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONObject().put(null, "foo");
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testStrings() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", "true");
-        object.put("bar", "5.5");
-        object.put("baz", "9223372036854775806");
-        object.put("quux", "null");
-        object.put("height", "5\"8' tall");
-
-        assertTrue(object.toString().contains("\"foo\":\"true\""));
-        assertTrue(object.toString().contains("\"bar\":\"5.5\""));
-        assertTrue(object.toString().contains("\"baz\":\"9223372036854775806\""));
-        assertTrue(object.toString().contains("\"quux\":\"null\""));
-        assertTrue(object.toString().contains("\"height\":\"5\\\"8' tall\""));
-
-        assertEquals("true", object.get("foo"));
-        assertEquals("null", object.getString("quux"));
-        assertEquals("5\"8' tall", object.getString("height"));
-        assertEquals("true", object.opt("foo"));
-        assertEquals("5.5", object.optString("bar"));
-        assertEquals("true", object.optString("foo", "x"));
-        assertFalse(object.isNull("foo"));
-
-        assertEquals(true, object.getBoolean("foo"));
-        assertEquals(true, object.optBoolean("foo"));
-        assertEquals(true, object.optBoolean("foo", false));
-        assertEquals(0, object.optInt("foo"));
-        assertEquals(-2, object.optInt("foo", -2));
-
-        assertEquals(5.5d, object.getDouble("bar"));
-        assertEquals(5L, object.getLong("bar"));
-        assertEquals(5, object.getInt("bar"));
-        assertEquals(5, object.optInt("bar", 3));
-
-        // The last digit of the string is a 6 but getLong returns a 7. It's probably parsing as a
-        // double and then converting that to a long. This is consistent with JavaScript.
-        assertEquals(9223372036854775807L, object.getLong("baz"));
-        assertEquals(9.223372036854776E18, object.getDouble("baz"));
-        assertEquals(Integer.MAX_VALUE, object.getInt("baz"));
-
-        assertFalse(object.isNull("quux"));
-        try {
-            object.getDouble("quux");
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals(Double.NaN, object.optDouble("quux"));
-        assertEquals(-1.0d, object.optDouble("quux", -1.0d));
-
-        object.put("foo", "TRUE");
-        assertEquals(true, object.getBoolean("foo"));
-    }
-
-    public void testJSONObjects() throws JSONException {
-        JSONObject object = new JSONObject();
-
-        JSONArray a = new JSONArray();
-        JSONObject b = new JSONObject();
-        object.put("foo", a);
-        object.put("bar", b);
-
-        assertSame(a, object.getJSONArray("foo"));
-        assertSame(b, object.getJSONObject("bar"));
-        try {
-            object.getJSONObject("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.getJSONArray("bar");
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals(a, object.optJSONArray("foo"));
-        assertEquals(b, object.optJSONObject("bar"));
-        assertEquals(null, object.optJSONArray("bar"));
-        assertEquals(null, object.optJSONObject("foo"));
-    }
-
-    public void testNullCoercionToString() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", JSONObject.NULL);
-        assertEquals("null", object.getString("foo"));
-    }
-
-    public void testArrayCoercion() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", "[true]");
-        try {
-            object.getJSONArray("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testObjectCoercion() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", "{}");
-        try {
-            object.getJSONObject("foo");
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testAccumulateValueChecking() throws JSONException {
-        JSONObject object = new JSONObject();
-        try {
-            object.accumulate("foo", Double.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-        object.accumulate("foo", 1);
-        try {
-            object.accumulate("foo", Double.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-        object.accumulate("foo", 2);
-        try {
-            object.accumulate("foo", Double.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testToJSONArray() throws JSONException {
-        JSONObject object = new JSONObject();
-        Object value = new Object();
-        object.put("foo", true);
-        object.put("bar", 5.0d);
-        object.put("baz", -0.0d);
-        object.put("quux", value);
-
-        JSONArray names = new JSONArray();
-        names.put("baz");
-        names.put("quux");
-        names.put("foo");
-
-        JSONArray array = object.toJSONArray(names);
-        assertEquals(-0.0d, array.get(0));
-        assertEquals(value, array.get(1));
-        assertEquals(true, array.get(2));
-
-        object.put("foo", false);
-        assertEquals(true, array.get(2));
-    }
-
-    public void testToJSONArrayMissingNames() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", true);
-        object.put("bar", 5.0d);
-        object.put("baz", JSONObject.NULL);
-
-        JSONArray names = new JSONArray();
-        names.put("bar");
-        names.put("foo");
-        names.put("quux");
-        names.put("baz");
-
-        JSONArray array = object.toJSONArray(names);
-        assertEquals(4, array.length());
-
-        assertEquals(5.0d, array.get(0));
-        assertEquals(true, array.get(1));
-        try {
-            array.get(2);
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals(JSONObject.NULL, array.get(3));
-    }
-
-    public void testToJSONArrayNull() throws JSONException {
-        JSONObject object = new JSONObject();
-        assertEquals(null, object.toJSONArray(null));
-        object.put("foo", 5);
-        try {
-            object.toJSONArray(null);
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testToJSONArrayEndsUpEmpty() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", 5);
-        JSONArray array = new JSONArray();
-        array.put("bar");
-        assertEquals(1, object.toJSONArray(array).length());
-    }
-
-    public void testToJSONArrayNonString() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", 5);
-        object.put("null", 10);
-        object.put("false", 15);
-
-        JSONArray names = new JSONArray();
-        names.put(JSONObject.NULL);
-        names.put(false);
-        names.put("foo");
-
-        // array elements are converted to strings to do name lookups on the map!
-        JSONArray array = object.toJSONArray(names);
-        assertEquals(3, array.length());
-        assertEquals(10, array.get(0));
-        assertEquals(15, array.get(1));
-        assertEquals(5, array.get(2));
-    }
-
-    public void testPutUnsupportedNumbers() throws JSONException {
-        JSONObject object = new JSONObject();
-        try {
-            object.put("foo", Double.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.put("foo", Double.NEGATIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.put("foo", Double.POSITIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testPutUnsupportedNumbersAsObjects() throws JSONException {
-        JSONObject object = new JSONObject();
-        try {
-            object.put("foo", (Double) Double.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.put("foo", (Double) Double.NEGATIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            object.put("foo", (Double) Double.POSITIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    /**
-     * Although JSONObject is usually defensive about which numbers it accepts,
-     * it doesn't check inputs in its constructor.
-     */
-    public void testCreateWithUnsupportedNumbers() throws JSONException {
-        Map<String, Object> contents = new HashMap<String, Object>();
-        contents.put("foo", Double.NaN);
-        contents.put("bar", Double.NEGATIVE_INFINITY);
-        contents.put("baz", Double.POSITIVE_INFINITY);
-
-        JSONObject object = new JSONObject(contents);
-        assertEquals(Double.NaN, object.get("foo"));
-        assertEquals(Double.NEGATIVE_INFINITY, object.get("bar"));
-        assertEquals(Double.POSITIVE_INFINITY, object.get("baz"));
-    }
-
-    public void testToStringWithUnsupportedNumbers() {
-        // when the object contains an unsupported number, toString returns null!
-        JSONObject object = new JSONObject(Collections.singletonMap("foo", Double.NaN));
-        assertEquals(null, object.toString());
-    }
-
-    public void testMapConstructorCopiesContents() throws JSONException {
-        Map<String, Object> contents = new HashMap<String, Object>();
-        contents.put("foo", 5);
-        JSONObject object = new JSONObject(contents);
-        contents.put("foo", 10);
-        assertEquals(5, object.get("foo"));
-    }
-
-    public void testMapConstructorWithBogusEntries() {
-        Map<Object, Object> contents = new HashMap<Object, Object>();
-        contents.put(5, 5);
-
-        try {
-            new JSONObject(contents);
-            fail("JSONObject constructor doesn't validate its input!");
-        } catch (Exception e) {
-        }
-    }
-
-    public void testTokenerConstructor() throws JSONException {
-        JSONObject object = new JSONObject(new JSONTokener("{\"foo\": false}"));
-        assertEquals(1, object.length());
-        assertEquals(false, object.get("foo"));
-    }
-
-    public void testTokenerConstructorWrongType() throws JSONException {
-        try {
-            new JSONObject(new JSONTokener("[\"foo\", false]"));
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testTokenerConstructorNull() throws JSONException {
-        try {
-            new JSONObject((JSONTokener) null);
-            fail();
-        } catch (NullPointerException e) {
-        }
-    }
-
-    public void testTokenerConstructorParseFail() {
-        try {
-            new JSONObject(new JSONTokener("{"));
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testStringConstructor() throws JSONException {
-        JSONObject object = new JSONObject("{\"foo\": false}");
-        assertEquals(1, object.length());
-        assertEquals(false, object.get("foo"));
-    }
-
-    public void testStringConstructorWrongType() throws JSONException {
-        try {
-            new JSONObject("[\"foo\", false]");
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testStringConstructorNull() throws JSONException {
-        try {
-            new JSONObject((String) null);
-            fail();
-        } catch (NullPointerException e) {
-        }
-    }
-
-    public void testStringConstructorParseFail() {
-        try {
-            new JSONObject("{");
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testCopyConstructor() throws JSONException {
-        JSONObject source = new JSONObject();
-        source.put("a", JSONObject.NULL);
-        source.put("b", false);
-        source.put("c", 5);
-
-        JSONObject copy = new JSONObject(source, new String[] { "a", "c" });
-        assertEquals(2, copy.length());
-        assertEquals(JSONObject.NULL, copy.get("a"));
-        assertEquals(5, copy.get("c"));
-        assertEquals(null, copy.opt("b"));
-    }
-
-    public void testCopyConstructorMissingName() throws JSONException {
-        JSONObject source = new JSONObject();
-        source.put("a", JSONObject.NULL);
-        source.put("b", false);
-        source.put("c", 5);
-
-        JSONObject copy = new JSONObject(source, new String[]{ "a", "c", "d" });
-        assertEquals(2, copy.length());
-        assertEquals(JSONObject.NULL, copy.get("a"));
-        assertEquals(5, copy.get("c"));
-        assertEquals(0, copy.optInt("b"));
-    }
-
-    public void testAccumulateMutatesInPlace() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", 5);
-        object.accumulate("foo", 6);
-        JSONArray array = object.getJSONArray("foo");
-        assertEquals("[5,6]", array.toString());
-        object.accumulate("foo", 7);
-        assertEquals("[5,6,7]", array.toString());
-    }
-
-    public void testAccumulateExistingArray() throws JSONException {
-        JSONArray array = new JSONArray();
-        JSONObject object = new JSONObject();
-        object.put("foo", array);
-        object.accumulate("foo", 5);
-        assertEquals("[5]", array.toString());
-    }
-
-    public void testAccumulatePutArray() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.accumulate("foo", 5);
-        assertEquals("{\"foo\":5}", object.toString());
-        object.accumulate("foo", new JSONArray());
-        assertEquals("{\"foo\":[5,[]]}", object.toString());
-    }
-
-    public void testAccumulateNull() {
-        JSONObject object = new JSONObject();
-        try {
-            object.accumulate(null, 5);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testEmptyStringKey() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("", 5);
-        assertEquals(5, object.get(""));
-        assertEquals("{\"\":5}", object.toString());
-    }
-
-    public void testNullValue() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", JSONObject.NULL);
-        object.put("bar", (Collection) null);
-
-        // there are two ways to represent null; each behaves differently!
-        assertTrue(object.has("foo"));
-        assertFalse(object.has("bar"));
-        assertTrue(object.isNull("foo"));
-        assertTrue(object.isNull("bar"));
-    }
-
-    public void testNullValue_equalsAndHashCode() {
-        assertTrue(JSONObject.NULL.equals(null)); // guaranteed by javadoc
-        // not guaranteed by javadoc, but seems like a good idea
-        assertEquals(Objects.hashCode(null), JSONObject.NULL.hashCode());
-    }
-
-    public void testHas() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", 5);
-        assertTrue(object.has("foo"));
-        assertFalse(object.has("bar"));
-        assertFalse(object.has(null));
-    }
-
-    public void testOptNull() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", "bar");
-        assertEquals(null, object.opt(null));
-        assertEquals(false, object.optBoolean(null));
-        assertEquals(Double.NaN, object.optDouble(null));
-        assertEquals(0, object.optInt(null));
-        assertEquals(0L, object.optLong(null));
-        assertEquals(null, object.optJSONArray(null));
-        assertEquals(null, object.optJSONObject(null));
-        assertEquals("", object.optString(null));
-        assertEquals(true, object.optBoolean(null, true));
-        assertEquals(0.0d, object.optDouble(null, 0.0d));
-        assertEquals(1, object.optInt(null, 1));
-        assertEquals(1L, object.optLong(null, 1L));
-        assertEquals("baz", object.optString(null, "baz"));
-    }
-
-    public void testToStringWithIndentFactor() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", new JSONArray(Arrays.asList(5, 6)));
-        object.put("bar", new JSONObject());
-        String foobar = "{\n" +
-                "     \"foo\": [\n" +
-                "          5,\n" +
-                "          6\n" +
-                "     ],\n" +
-                "     \"bar\": {}\n" +
-                "}";
-        String barfoo = "{\n" +
-                "     \"bar\": {},\n" +
-                "     \"foo\": [\n" +
-                "          5,\n" +
-                "          6\n" +
-                "     ]\n" +
-                "}";
-        String string = object.toString(5);
-        assertTrue(string, foobar.equals(string) || barfoo.equals(string));
-    }
-
-    public void testNames() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", 5);
-        object.put("bar", 6);
-        object.put("baz", 7);
-        JSONArray array = object.names();
-        assertTrue(array.toString().contains("foo"));
-        assertTrue(array.toString().contains("bar"));
-        assertTrue(array.toString().contains("baz"));
-    }
-
-    public void testKeysEmptyObject() {
-        JSONObject object = new JSONObject();
-        assertFalse(object.keys().hasNext());
-        try {
-            object.keys().next();
-            fail();
-        } catch (NoSuchElementException e) {
-        }
-    }
-
-    public void testKeys() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", 5);
-        object.put("bar", 6);
-        object.put("foo", 7);
-
-        @SuppressWarnings("unchecked")
-        Iterator<String> keys = (Iterator<String>) object.keys();
-        Set<String> result = new HashSet<String>();
-        assertTrue(keys.hasNext());
-        result.add(keys.next());
-        assertTrue(keys.hasNext());
-        result.add(keys.next());
-        assertFalse(keys.hasNext());
-        assertEquals(new HashSet<String>(Arrays.asList("foo", "bar")), result);
-
-        try {
-            keys.next();
-            fail();
-        } catch (NoSuchElementException e) {
-        }
-    }
-
-    public void testMutatingKeysMutatesObject() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", 5);
-        Iterator keys = object.keys();
-        keys.next();
-        keys.remove();
-        assertEquals(0, object.length());
-    }
-
-    public void testQuote() {
-        // covered by JSONStringerTest.testEscaping
-    }
-
-    public void testQuoteNull() throws JSONException {
-        assertEquals("\"\"", JSONObject.quote(null));
-    }
-
-    public void testNumberToString() throws JSONException {
-        assertEquals("5", JSONObject.numberToString(5));
-        assertEquals("-0", JSONObject.numberToString(-0.0d));
-        assertEquals("9223372036854775806", JSONObject.numberToString(9223372036854775806L));
-        assertEquals("4.9E-324", JSONObject.numberToString(Double.MIN_VALUE));
-        assertEquals("1.7976931348623157E308", JSONObject.numberToString(Double.MAX_VALUE));
-        try {
-            JSONObject.numberToString(Double.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            JSONObject.numberToString(Double.NEGATIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            JSONObject.numberToString(Double.POSITIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals("0.001", JSONObject.numberToString(new BigDecimal("0.001")));
-        assertEquals("9223372036854775806",
-                JSONObject.numberToString(new BigInteger("9223372036854775806")));
-        try {
-            JSONObject.numberToString(null);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void test_wrap() throws Exception {
-        assertEquals(JSONObject.NULL, JSONObject.wrap(null));
-
-        JSONArray a = new JSONArray();
-        assertEquals(a, JSONObject.wrap(a));
-
-        JSONObject o = new JSONObject();
-        assertEquals(o, JSONObject.wrap(o));
-
-        assertEquals(JSONObject.NULL, JSONObject.wrap(JSONObject.NULL));
-
-        assertTrue(JSONObject.wrap(new byte[0]) instanceof JSONArray);
-        assertTrue(JSONObject.wrap(new ArrayList<String>()) instanceof JSONArray);
-        assertTrue(JSONObject.wrap(new HashMap<String, String>()) instanceof JSONObject);
-        assertTrue(JSONObject.wrap(Double.valueOf(0)) instanceof Double);
-        assertTrue(JSONObject.wrap("hello") instanceof String);
-    }
-
-    // https://code.google.com/p/android/issues/detail?id=55114
-    public void test_toString_listAsMapValue() throws Exception {
-        ArrayList<Object> list = new ArrayList<Object>();
-        list.add("a");
-        list.add(new ArrayList<String>());
-        Map<String, Object> map = new TreeMap<String, Object>();
-        map.put("x", "l");
-        map.put("y", list);
-        assertEquals("{\"x\":\"l\",\"y\":[\"a\",[]]}", new JSONObject(map).toString());
-    }
-
-    public void testAppendExistingInvalidKey() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("foo", 5);
-        try {
-            object.append("foo", 6);
-            fail();
-        } catch (JSONException expected) {
-        }
-    }
-
-    public void testAppendExistingArray() throws JSONException {
-        JSONArray array = new JSONArray();
-        JSONObject object = new JSONObject();
-        object.put("foo", array);
-        object.append("foo", 5);
-        assertEquals("[5]", array.toString());
-    }
-
-    public void testAppendPutArray() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.append("foo", 5);
-        assertEquals("{\"foo\":[5]}", object.toString());
-        object.append("foo", new JSONArray());
-        assertEquals("{\"foo\":[5,[]]}", object.toString());
-    }
-
-    public void testAppendNull() {
-        JSONObject object = new JSONObject();
-        try {
-            object.append(null, 5);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    // https://code.google.com/p/android/issues/detail?id=103641
-    public void testInvalidUnicodeEscape() {
-        try {
-            new JSONObject("{\"q\":\"\\u\", \"r\":[]}");
-            fail();
-        } catch (JSONException expected) {
-        }
-    }
-}
diff --git a/org/json/JSONStringerTest.java b/org/json/JSONStringerTest.java
deleted file mode 100644
index 1e4e0ec..0000000
--- a/org/json/JSONStringerTest.java
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open 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 org.json;
-
-import junit.framework.TestCase;
-
-/**
- * This black box test was written without inspecting the non-free org.json sourcecode.
- */
-public class JSONStringerTest extends TestCase {
-
-    public void testEmptyStringer() {
-        // why isn't this the empty string?
-        assertNull(new JSONStringer().toString());
-    }
-
-    public void testValueJSONNull() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.array();
-        stringer.value(JSONObject.NULL);
-        stringer.endArray();
-        assertEquals("[null]", stringer.toString());
-    }
-
-    public void testEmptyObject() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.object();
-        stringer.endObject();
-        assertEquals("{}", stringer.toString());
-    }
-
-    public void testEmptyArray() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.array();
-        stringer.endArray();
-        assertEquals("[]", stringer.toString());
-    }
-
-    public void testArray() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.array();
-        stringer.value(false);
-        stringer.value(5.0);
-        stringer.value(5L);
-        stringer.value("five");
-        stringer.value(null);
-        stringer.endArray();
-        assertEquals("[false,5,5,\"five\",null]", stringer.toString());
-    }
-
-    public void testValueObjectMethods() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.array();
-        stringer.value(Boolean.FALSE);
-        stringer.value(Double.valueOf(5.0));
-        stringer.value(Long.valueOf(5L));
-        stringer.endArray();
-        assertEquals("[false,5,5]", stringer.toString());
-    }
-
-    public void testKeyValue() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.object();
-        stringer.key("a").value(false);
-        stringer.key("b").value(5.0);
-        stringer.key("c").value(5L);
-        stringer.key("d").value("five");
-        stringer.key("e").value(null);
-        stringer.endObject();
-        assertEquals("{\"a\":false," +
-                "\"b\":5," +
-                "\"c\":5," +
-                "\"d\":\"five\"," +
-                "\"e\":null}", stringer.toString());
-    }
-
-    /**
-     * Test what happens when extreme values are emitted. Such values are likely
-     * to be rounded during parsing.
-     */
-    public void testNumericRepresentations() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.array();
-        stringer.value(Long.MAX_VALUE);
-        stringer.value(Double.MIN_VALUE);
-        stringer.endArray();
-        assertEquals("[9223372036854775807,4.9E-324]", stringer.toString());
-    }
-
-    public void testWeirdNumbers() throws JSONException {
-        try {
-            new JSONStringer().array().value(Double.NaN);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONStringer().array().value(Double.NEGATIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONStringer().array().value(Double.POSITIVE_INFINITY);
-            fail();
-        } catch (JSONException e) {
-        }
-
-        JSONStringer stringer = new JSONStringer();
-        stringer.array();
-        stringer.value(-0.0d);
-        stringer.value(0.0d);
-        stringer.endArray();
-        assertEquals("[-0,0]", stringer.toString());
-    }
-
-    public void testMismatchedScopes() {
-        try {
-            new JSONStringer().key("a");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONStringer().value("a");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONStringer().endObject();
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONStringer().endArray();
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONStringer().array().endObject();
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONStringer().object().endArray();
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONStringer().object().key("a").key("a");
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONStringer().object().value(false);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testNullKey() {
-        try {
-            new JSONStringer().object().key(null);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testRepeatedKey() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.object();
-        stringer.key("a").value(true);
-        stringer.key("a").value(false);
-        stringer.endObject();
-        // JSONStringer doesn't attempt to detect duplicates
-        assertEquals("{\"a\":true,\"a\":false}", stringer.toString());
-    }
-
-    public void testEmptyKey() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.object();
-        stringer.key("").value(false);
-        stringer.endObject();
-        assertEquals("{\"\":false}", stringer.toString()); // legit behaviour!
-    }
-
-    public void testEscaping() throws JSONException {
-        assertEscapedAllWays("a", "a");
-        assertEscapedAllWays("a\\\"", "a\"");
-        assertEscapedAllWays("\\\"", "\"");
-        assertEscapedAllWays(":", ":");
-        assertEscapedAllWays(",", ",");
-        assertEscapedAllWays("\\b", "\b");
-        assertEscapedAllWays("\\f", "\f");
-        assertEscapedAllWays("\\n", "\n");
-        assertEscapedAllWays("\\r", "\r");
-        assertEscapedAllWays("\\t", "\t");
-        assertEscapedAllWays(" ", " ");
-        assertEscapedAllWays("\\\\", "\\");
-        assertEscapedAllWays("{", "{");
-        assertEscapedAllWays("}", "}");
-        assertEscapedAllWays("[", "[");
-        assertEscapedAllWays("]", "]");
-        assertEscapedAllWays("\\u0000", "\0");
-        assertEscapedAllWays("\\u0019", "\u0019");
-        assertEscapedAllWays(" ", "\u0020");
-    }
-
-    private void assertEscapedAllWays(String escaped, String original) throws JSONException {
-        assertEquals("{\"" + escaped + "\":false}",
-                new JSONStringer().object().key(original).value(false).endObject().toString());
-        assertEquals("{\"a\":\"" + escaped + "\"}",
-                new JSONStringer().object().key("a").value(original).endObject().toString());
-        assertEquals("[\"" + escaped + "\"]",
-                new JSONStringer().array().value(original).endArray().toString());
-        assertEquals("\"" + escaped + "\"", JSONObject.quote(original));
-    }
-
-    public void testJSONArrayAsValue() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put(false);
-        JSONStringer stringer = new JSONStringer();
-        stringer.array();
-        stringer.value(array);
-        stringer.endArray();
-        assertEquals("[[false]]", stringer.toString());
-    }
-
-    public void testJSONObjectAsValue() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("a", false);
-        JSONStringer stringer = new JSONStringer();
-        stringer.object();
-        stringer.key("b").value(object);
-        stringer.endObject();
-        assertEquals("{\"b\":{\"a\":false}}", stringer.toString());
-    }
-
-    public void testArrayNestingMaxDepthSupports20() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        for (int i = 0; i < 20; i++) {
-            stringer.array();
-        }
-        for (int i = 0; i < 20; i++) {
-            stringer.endArray();
-        }
-        assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringer.toString());
-
-        stringer = new JSONStringer();
-        for (int i = 0; i < 20; i++) {
-            stringer.array();
-        }
-    }
-
-    public void testObjectNestingMaxDepthSupports20() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        for (int i = 0; i < 20; i++) {
-            stringer.object();
-            stringer.key("a");
-        }
-        stringer.value(false);
-        for (int i = 0; i < 20; i++) {
-            stringer.endObject();
-        }
-        assertEquals("{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":" +
-                "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":false" +
-                "}}}}}}}}}}}}}}}}}}}}", stringer.toString());
-
-        stringer = new JSONStringer();
-        for (int i = 0; i < 20; i++) {
-            stringer.object();
-            stringer.key("a");
-        }
-    }
-
-    public void testMixedMaxDepthSupports20() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        for (int i = 0; i < 20; i+=2) {
-            stringer.array();
-            stringer.object();
-            stringer.key("a");
-        }
-        stringer.value(false);
-        for (int i = 0; i < 20; i+=2) {
-            stringer.endObject();
-            stringer.endArray();
-        }
-        assertEquals("[{\"a\":[{\"a\":[{\"a\":[{\"a\":[{\"a\":" +
-                "[{\"a\":[{\"a\":[{\"a\":[{\"a\":[{\"a\":false" +
-                "}]}]}]}]}]}]}]}]}]}]", stringer.toString());
-
-        stringer = new JSONStringer();
-        for (int i = 0; i < 20; i+=2) {
-            stringer.array();
-            stringer.object();
-            stringer.key("a");
-        }
-    }
-
-    public void testMaxDepthWithArrayValue() throws JSONException {
-        JSONArray array = new JSONArray();
-        array.put(false);
-
-        JSONStringer stringer = new JSONStringer();
-        for (int i = 0; i < 20; i++) {
-            stringer.array();
-        }
-        stringer.value(array);
-        for (int i = 0; i < 20; i++) {
-            stringer.endArray();
-        }
-        assertEquals("[[[[[[[[[[[[[[[[[[[[[false]]]]]]]]]]]]]]]]]]]]]", stringer.toString());
-    }
-
-    public void testMaxDepthWithObjectValue() throws JSONException {
-        JSONObject object = new JSONObject();
-        object.put("a", false);
-        JSONStringer stringer = new JSONStringer();
-        for (int i = 0; i < 20; i++) {
-            stringer.object();
-            stringer.key("b");
-        }
-        stringer.value(object);
-        for (int i = 0; i < 20; i++) {
-            stringer.endObject();
-        }
-        assertEquals("{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":" +
-                "{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":{\"b\":" +
-                "{\"a\":false}}}}}}}}}}}}}}}}}}}}}", stringer.toString());
-    }
-
-    public void testMultipleRoots() throws JSONException {
-        JSONStringer stringer = new JSONStringer();
-        stringer.array();
-        stringer.endArray();
-        try {
-            stringer.object();
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-}
diff --git a/org/json/JSONTokenerTest.java b/org/json/JSONTokenerTest.java
deleted file mode 100644
index 1152e46..0000000
--- a/org/json/JSONTokenerTest.java
+++ /dev/null
@@ -1,619 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open 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 org.json;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.TestCase;
-
-/**
- * This black box test was written without inspecting the non-free org.json sourcecode.
- */
-public class JSONTokenerTest extends TestCase {
-
-    public void testNulls() throws JSONException {
-        // JSONTokener accepts null, only to fail later on almost all APIs!
-        new JSONTokener(null).back();
-
-        try {
-            new JSONTokener(null).more();
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).next();
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).next(3);
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).next('A');
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).nextClean();
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).nextString('"');
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).nextTo('A');
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).nextTo("ABC");
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).nextValue();
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).skipPast("ABC");
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            new JSONTokener(null).skipTo('A');
-            fail();
-        } catch (NullPointerException e) {
-        }
-
-        assertEquals("foo! at character 0 of null",
-                new JSONTokener(null).syntaxError("foo!").getMessage());
-
-        assertEquals(" at character 0 of null", new JSONTokener(null).toString());
-    }
-
-    public void testEmptyString() throws JSONException {
-        JSONTokener backTokener = new JSONTokener("");
-        backTokener.back();
-        assertEquals(" at character 0 of ", backTokener.toString());
-        assertFalse(new JSONTokener("").more());
-        assertEquals('\0', new JSONTokener("").next());
-        try {
-            new JSONTokener("").next(3);
-            fail();
-        } catch (JSONException expected) {
-        }
-        try {
-            new JSONTokener("").next('A');
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals('\0', new JSONTokener("").nextClean());
-        try {
-            new JSONTokener("").nextString('"');
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals("", new JSONTokener("").nextTo('A'));
-        assertEquals("", new JSONTokener("").nextTo("ABC"));
-        try {
-            new JSONTokener("").nextValue();
-            fail();
-        } catch (JSONException e) {
-        }
-        new JSONTokener("").skipPast("ABC");
-        assertEquals('\0', new JSONTokener("").skipTo('A'));
-        assertEquals("foo! at character 0 of ",
-                new JSONTokener("").syntaxError("foo!").getMessage());
-        assertEquals(" at character 0 of ", new JSONTokener("").toString());
-    }
-
-    public void testCharacterNavigation() throws JSONException {
-        JSONTokener abcdeTokener = new JSONTokener("ABCDE");
-        assertEquals('A', abcdeTokener.next());
-        assertEquals('B', abcdeTokener.next('B'));
-        assertEquals("CD", abcdeTokener.next(2));
-        try {
-            abcdeTokener.next(2);
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals('E', abcdeTokener.nextClean());
-        assertEquals('\0', abcdeTokener.next());
-        assertFalse(abcdeTokener.more());
-        abcdeTokener.back();
-        assertTrue(abcdeTokener.more());
-        assertEquals('E', abcdeTokener.next());
-    }
-
-    public void testBackNextAndMore() throws JSONException {
-        JSONTokener abcTokener = new JSONTokener("ABC");
-        assertTrue(abcTokener.more());
-        abcTokener.next();
-        abcTokener.next();
-        assertTrue(abcTokener.more());
-        abcTokener.next();
-        assertFalse(abcTokener.more());
-        abcTokener.back();
-        assertTrue(abcTokener.more());
-        abcTokener.next();
-        assertFalse(abcTokener.more());
-        abcTokener.back();
-        abcTokener.back();
-        abcTokener.back();
-        abcTokener.back(); // you can back up before the beginning of a String!
-        assertEquals('A', abcTokener.next());
-    }
-
-    public void testNextMatching() throws JSONException {
-        JSONTokener abcdTokener = new JSONTokener("ABCD");
-        assertEquals('A', abcdTokener.next('A'));
-        try {
-            abcdTokener.next('C'); // although it failed, this op consumes a character of input
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals('C', abcdTokener.next('C'));
-        assertEquals('D', abcdTokener.next('D'));
-        try {
-            abcdTokener.next('E');
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testNextN() throws JSONException {
-        JSONTokener abcdeTokener = new JSONTokener("ABCDEF");
-        assertEquals("", abcdeTokener.next(0));
-        try {
-            abcdeTokener.next(7);
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals("ABC", abcdeTokener.next(3));
-        try {
-            abcdeTokener.next(4);
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testNextNWithAllRemaining() throws JSONException {
-        JSONTokener tokener = new JSONTokener("ABCDEF");
-        tokener.next(3);
-        try {
-            tokener.next(3);
-        } catch (JSONException e) {
-            AssertionFailedError error = new AssertionFailedError("off-by-one error?");
-            error.initCause(e);
-            throw error;
-        }
-    }
-
-    public void testNext0() throws JSONException {
-        JSONTokener tokener = new JSONTokener("ABCDEF");
-        tokener.next(5);
-        tokener.next();
-        try {
-            tokener.next(0);
-        } catch (JSONException e) {
-            Error error = new AssertionFailedError("Returning an empty string should be valid");
-            error.initCause(e);
-            throw error;
-        }
-    }
-
-    public void testNextCleanComments() throws JSONException {
-        JSONTokener tokener = new JSONTokener(
-                "  A  /*XX*/B/*XX//XX\n//XX\nXX*/C//X//X//X\nD/*X*///X\n");
-        assertEquals('A', tokener.nextClean());
-        assertEquals('B', tokener.nextClean());
-        assertEquals('C', tokener.nextClean());
-        assertEquals('D', tokener.nextClean());
-        assertEquals('\0', tokener.nextClean());
-    }
-
-    public void testNextCleanNestedCStyleComments() throws JSONException {
-        JSONTokener tokener = new JSONTokener("A /* B /* C */ D */ E");
-        assertEquals('A', tokener.nextClean());
-        assertEquals('D', tokener.nextClean());
-        assertEquals('*', tokener.nextClean());
-        assertEquals('/', tokener.nextClean());
-        assertEquals('E', tokener.nextClean());
-    }
-
-    /**
-     * Some applications rely on parsing '#' to lead an end-of-line comment.
-     * http://b/2571423
-     */
-    public void testNextCleanHashComments() throws JSONException {
-        JSONTokener tokener = new JSONTokener("A # B */ /* C */ \nD #");
-        assertEquals('A', tokener.nextClean());
-        assertEquals('D', tokener.nextClean());
-        assertEquals('\0', tokener.nextClean());
-    }
-
-    public void testNextCleanCommentsTrailingSingleSlash() throws JSONException {
-        JSONTokener tokener = new JSONTokener(" / S /");
-        assertEquals('/', tokener.nextClean());
-        assertEquals('S', tokener.nextClean());
-        assertEquals('/', tokener.nextClean());
-        assertEquals("nextClean doesn't consume a trailing slash",
-                '\0', tokener.nextClean());
-    }
-
-    public void testNextCleanTrailingOpenComment() throws JSONException {
-        try {
-            new JSONTokener("  /* ").nextClean();
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals('\0', new JSONTokener("  // ").nextClean());
-    }
-
-    public void testNextCleanNewlineDelimiters() throws JSONException {
-        assertEquals('B', new JSONTokener("  // \r\n  B ").nextClean());
-        assertEquals('B', new JSONTokener("  // \n  B ").nextClean());
-        assertEquals('B', new JSONTokener("  // \r  B ").nextClean());
-    }
-
-    public void testNextCleanSkippedWhitespace() throws JSONException {
-        assertEquals("character tabulation", 'A', new JSONTokener("\tA").nextClean());
-        assertEquals("line feed",            'A', new JSONTokener("\nA").nextClean());
-        assertEquals("carriage return",      'A', new JSONTokener("\rA").nextClean());
-        assertEquals("space",                'A', new JSONTokener(" A").nextClean());
-    }
-
-    /**
-     * Tests which characters tokener treats as ignorable whitespace. See Kevin Bourrillion's
-     * <a href="https://spreadsheets.google.com/pub?key=pd8dAQyHbdewRsnE5x5GzKQ">list
-     * of whitespace characters</a>.
-     */
-    public void testNextCleanRetainedWhitespace() throws JSONException {
-        assertNotClean("null",                      '\u0000');
-        assertNotClean("next line",                 '\u0085');
-        assertNotClean("non-breaking space",        '\u00a0');
-        assertNotClean("ogham space mark",          '\u1680');
-        assertNotClean("mongolian vowel separator", '\u180e');
-        assertNotClean("en quad",                   '\u2000');
-        assertNotClean("em quad",                   '\u2001');
-        assertNotClean("en space",                  '\u2002');
-        assertNotClean("em space",                  '\u2003');
-        assertNotClean("three-per-em space",        '\u2004');
-        assertNotClean("four-per-em space",         '\u2005');
-        assertNotClean("six-per-em space",          '\u2006');
-        assertNotClean("figure space",              '\u2007');
-        assertNotClean("punctuation space",         '\u2008');
-        assertNotClean("thin space",                '\u2009');
-        assertNotClean("hair space",                '\u200a');
-        assertNotClean("zero-width space",          '\u200b');
-        assertNotClean("left-to-right mark",        '\u200e');
-        assertNotClean("right-to-left mark",        '\u200f');
-        assertNotClean("line separator",            '\u2028');
-        assertNotClean("paragraph separator",       '\u2029');
-        assertNotClean("narrow non-breaking space", '\u202f');
-        assertNotClean("medium mathematical space", '\u205f');
-        assertNotClean("ideographic space",         '\u3000');
-        assertNotClean("line tabulation",           '\u000b');
-        assertNotClean("form feed",                 '\u000c');
-        assertNotClean("information separator 4",   '\u001c');
-        assertNotClean("information separator 3",   '\u001d');
-        assertNotClean("information separator 2",   '\u001e');
-        assertNotClean("information separator 1",   '\u001f');
-    }
-
-    private void assertNotClean(String name, char c) throws JSONException {
-        assertEquals("The character " + name + " is not whitespace according to the JSON spec.",
-                c, new JSONTokener(new String(new char[] { c, 'A' })).nextClean());
-    }
-
-    public void testNextString() throws JSONException {
-        assertEquals("", new JSONTokener("'").nextString('\''));
-        assertEquals("", new JSONTokener("\"").nextString('\"'));
-        assertEquals("ABC", new JSONTokener("ABC'DEF").nextString('\''));
-        assertEquals("ABC", new JSONTokener("ABC'''DEF").nextString('\''));
-
-        // nextString permits slash-escaping of arbitrary characters!
-        assertEquals("ABC", new JSONTokener("A\\B\\C'DEF").nextString('\''));
-
-        JSONTokener tokener = new JSONTokener(" 'abc' 'def' \"ghi\"");
-        tokener.next();
-        assertEquals('\'', tokener.next());
-        assertEquals("abc", tokener.nextString('\''));
-        tokener.next();
-        assertEquals('\'', tokener.next());
-        assertEquals("def", tokener.nextString('\''));
-        tokener.next();
-        assertEquals('"', tokener.next());
-        assertEquals("ghi", tokener.nextString('\"'));
-        assertFalse(tokener.more());
-    }
-
-    public void testNextStringNoDelimiter() throws JSONException {
-        try {
-            new JSONTokener("").nextString('\'');
-            fail();
-        } catch (JSONException e) {
-        }
-
-        JSONTokener tokener = new JSONTokener(" 'abc");
-        tokener.next();
-        tokener.next();
-        try {
-            tokener.next('\'');
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testNextStringEscapedQuote() throws JSONException {
-        try {
-            new JSONTokener("abc\\").nextString('"');
-            fail();
-        } catch (JSONException e) {
-        }
-
-        // we're mixing Java escaping like \" and JavaScript escaping like \\\"
-        // which makes these tests extra tricky to read!
-        assertEquals("abc\"def", new JSONTokener("abc\\\"def\"ghi").nextString('"'));
-        assertEquals("abc\\def", new JSONTokener("abc\\\\def\"ghi").nextString('"'));
-        assertEquals("abc/def", new JSONTokener("abc\\/def\"ghi").nextString('"'));
-        assertEquals("abc\bdef", new JSONTokener("abc\\bdef\"ghi").nextString('"'));
-        assertEquals("abc\fdef", new JSONTokener("abc\\fdef\"ghi").nextString('"'));
-        assertEquals("abc\ndef", new JSONTokener("abc\\ndef\"ghi").nextString('"'));
-        assertEquals("abc\rdef", new JSONTokener("abc\\rdef\"ghi").nextString('"'));
-        assertEquals("abc\tdef", new JSONTokener("abc\\tdef\"ghi").nextString('"'));
-    }
-
-    public void testNextStringUnicodeEscaped() throws JSONException {
-        // we're mixing Java escaping like \\ and JavaScript escaping like \\u
-        assertEquals("abc def", new JSONTokener("abc\\u0020def\"ghi").nextString('"'));
-        assertEquals("abcU0020def", new JSONTokener("abc\\U0020def\"ghi").nextString('"'));
-
-        // JSON requires 4 hex characters after a unicode escape
-        try {
-            new JSONTokener("abc\\u002\"").nextString('"');
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONTokener("abc\\u").nextString('"');
-            fail();
-        } catch (JSONException e) {
-        }
-        try {
-            new JSONTokener("abc\\u    \"").nextString('"');
-            fail();
-        } catch (JSONException e) {
-        }
-        assertEquals("abc\"def", new JSONTokener("abc\\u0022def\"ghi").nextString('"'));
-        try {
-            new JSONTokener("abc\\u000G\"").nextString('"');
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testNextStringNonQuote() throws JSONException {
-        assertEquals("AB", new JSONTokener("ABC").nextString('C'));
-        assertEquals("ABCD", new JSONTokener("AB\\CDC").nextString('C'));
-        assertEquals("AB\nC", new JSONTokener("AB\\nCn").nextString('n'));
-    }
-
-    public void testNextTo() throws JSONException {
-        assertEquals("ABC", new JSONTokener("ABCDEFG").nextTo("DHI"));
-        assertEquals("ABCDEF", new JSONTokener("ABCDEF").nextTo(""));
-
-        JSONTokener tokener = new JSONTokener("ABC\rDEF\nGHI\r\nJKL");
-        assertEquals("ABC", tokener.nextTo("M"));
-        assertEquals('\r', tokener.next());
-        assertEquals("DEF", tokener.nextTo("M"));
-        assertEquals('\n', tokener.next());
-        assertEquals("GHI", tokener.nextTo("M"));
-        assertEquals('\r', tokener.next());
-        assertEquals('\n', tokener.next());
-        assertEquals("JKL", tokener.nextTo("M"));
-
-        tokener = new JSONTokener("ABCDEFGHI");
-        assertEquals("ABC", tokener.nextTo("DEF"));
-        assertEquals("", tokener.nextTo("DEF"));
-        assertEquals('D', tokener.next());
-        assertEquals("", tokener.nextTo("DEF"));
-        assertEquals('E', tokener.next());
-        assertEquals("", tokener.nextTo("DEF"));
-        assertEquals('F', tokener.next());
-        assertEquals("GHI", tokener.nextTo("DEF"));
-        assertEquals("", tokener.nextTo("DEF"));
-
-        tokener = new JSONTokener(" \t \fABC \t DEF");
-        assertEquals("ABC", tokener.nextTo("DEF"));
-        assertEquals('D', tokener.next());
-
-        tokener = new JSONTokener(" \t \fABC \n DEF");
-        assertEquals("ABC", tokener.nextTo("\n"));
-        assertEquals("", tokener.nextTo("\n"));
-
-        tokener = new JSONTokener("");
-        try {
-            tokener.nextTo(null);
-            fail();
-        } catch (NullPointerException e) {
-        }
-    }
-
-    public void testNextToTrimming() {
-        assertEquals("ABC", new JSONTokener("\t ABC \tDEF").nextTo("DE"));
-        assertEquals("ABC", new JSONTokener("\t ABC \tDEF").nextTo('D'));
-    }
-
-    public void testNextToTrailing() {
-        assertEquals("ABC DEF", new JSONTokener("\t ABC DEF \t").nextTo("G"));
-        assertEquals("ABC DEF", new JSONTokener("\t ABC DEF \t").nextTo('G'));
-    }
-
-    public void testNextToDoesntStopOnNull() {
-        String message = "nextTo() shouldn't stop after \\0 characters";
-        JSONTokener tokener = new JSONTokener(" \0\t \fABC \n DEF");
-        assertEquals(message, "ABC", tokener.nextTo("D"));
-        assertEquals(message, '\n', tokener.next());
-        assertEquals(message, "", tokener.nextTo("D"));
-    }
-
-    public void testNextToConsumesNull() {
-        String message = "nextTo shouldn't consume \\0.";
-        JSONTokener tokener = new JSONTokener("ABC\0DEF");
-        assertEquals(message, "ABC", tokener.nextTo("\0"));
-        assertEquals(message, '\0', tokener.next());
-        assertEquals(message, "DEF", tokener.nextTo("\0"));
-    }
-
-    public void testSkipPast() {
-        JSONTokener tokener = new JSONTokener("ABCDEF");
-        tokener.skipPast("ABC");
-        assertEquals('D', tokener.next());
-        tokener.skipPast("EF");
-        assertEquals('\0', tokener.next());
-
-        tokener = new JSONTokener("ABCDEF");
-        tokener.skipPast("ABCDEF");
-        assertEquals('\0', tokener.next());
-
-        tokener = new JSONTokener("ABCDEF");
-        tokener.skipPast("G");
-        assertEquals('\0', tokener.next());
-
-        tokener = new JSONTokener("ABC\0ABC");
-        tokener.skipPast("ABC");
-        assertEquals('\0', tokener.next());
-        assertEquals('A', tokener.next());
-
-        tokener = new JSONTokener("\0ABC");
-        tokener.skipPast("ABC");
-        assertEquals('\0', tokener.next());
-
-        tokener = new JSONTokener("ABC\nDEF");
-        tokener.skipPast("DEF");
-        assertEquals('\0', tokener.next());
-
-        tokener = new JSONTokener("ABC");
-        tokener.skipPast("ABCDEF");
-        assertEquals('\0', tokener.next());
-
-        tokener = new JSONTokener("ABCDABCDABCD");
-        tokener.skipPast("ABC");
-        assertEquals('D', tokener.next());
-        tokener.skipPast("ABC");
-        assertEquals('D', tokener.next());
-        tokener.skipPast("ABC");
-        assertEquals('D', tokener.next());
-
-        tokener = new JSONTokener("");
-        try {
-            tokener.skipPast(null);
-            fail();
-        } catch (NullPointerException e) {
-        }
-    }
-
-    public void testSkipTo() {
-        JSONTokener tokener = new JSONTokener("ABCDEF");
-        tokener.skipTo('A');
-        assertEquals('A', tokener.next());
-        tokener.skipTo('D');
-        assertEquals('D', tokener.next());
-        tokener.skipTo('G');
-        assertEquals('E', tokener.next());
-        tokener.skipTo('A');
-        assertEquals('F', tokener.next());
-
-        tokener = new JSONTokener("ABC\nDEF");
-        tokener.skipTo('F');
-        assertEquals('F', tokener.next());
-
-        tokener = new JSONTokener("ABCfDEF");
-        tokener.skipTo('F');
-        assertEquals('F', tokener.next());
-
-        tokener = new JSONTokener("ABC/* DEF */");
-        tokener.skipTo('D');
-        assertEquals('D', tokener.next());
-    }
-
-    public void testSkipToStopsOnNull() {
-        JSONTokener tokener = new JSONTokener("ABC\0DEF");
-        tokener.skipTo('F');
-        assertEquals("skipTo shouldn't stop when it sees '\\0'", 'F', tokener.next());
-    }
-
-    public void testBomIgnoredAsFirstCharacterOfDocument() throws JSONException {
-        JSONTokener tokener = new JSONTokener("\ufeff[]");
-        JSONArray array = (JSONArray) tokener.nextValue();
-        assertEquals(0, array.length());
-    }
-
-    public void testBomTreatedAsCharacterInRestOfDocument() throws JSONException {
-        JSONTokener tokener = new JSONTokener("[\ufeff]");
-        JSONArray array = (JSONArray) tokener.nextValue();
-        assertEquals(1, array.length());
-    }
-
-    public void testDehexchar() {
-        assertEquals( 0, JSONTokener.dehexchar('0'));
-        assertEquals( 1, JSONTokener.dehexchar('1'));
-        assertEquals( 2, JSONTokener.dehexchar('2'));
-        assertEquals( 3, JSONTokener.dehexchar('3'));
-        assertEquals( 4, JSONTokener.dehexchar('4'));
-        assertEquals( 5, JSONTokener.dehexchar('5'));
-        assertEquals( 6, JSONTokener.dehexchar('6'));
-        assertEquals( 7, JSONTokener.dehexchar('7'));
-        assertEquals( 8, JSONTokener.dehexchar('8'));
-        assertEquals( 9, JSONTokener.dehexchar('9'));
-        assertEquals(10, JSONTokener.dehexchar('A'));
-        assertEquals(11, JSONTokener.dehexchar('B'));
-        assertEquals(12, JSONTokener.dehexchar('C'));
-        assertEquals(13, JSONTokener.dehexchar('D'));
-        assertEquals(14, JSONTokener.dehexchar('E'));
-        assertEquals(15, JSONTokener.dehexchar('F'));
-        assertEquals(10, JSONTokener.dehexchar('a'));
-        assertEquals(11, JSONTokener.dehexchar('b'));
-        assertEquals(12, JSONTokener.dehexchar('c'));
-        assertEquals(13, JSONTokener.dehexchar('d'));
-        assertEquals(14, JSONTokener.dehexchar('e'));
-        assertEquals(15, JSONTokener.dehexchar('f'));
-
-        for (int c = 0; c <= 0xFFFF; c++) {
-            if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
-                continue;
-            }
-            assertEquals("dehexchar " + c, -1, JSONTokener.dehexchar((char) c));
-        }
-    }
-}
diff --git a/org/json/ParsingTest.java b/org/json/ParsingTest.java
deleted file mode 100644
index 641f5b9..0000000
--- a/org/json/ParsingTest.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open 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 org.json;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import junit.framework.TestCase;
-
-public class ParsingTest extends TestCase {
-
-    public void testParsingNoObjects() {
-        try {
-            new JSONTokener("").nextValue();
-            fail();
-        } catch (JSONException e) {
-        }
-    }
-
-    public void testParsingLiterals() throws JSONException {
-        assertParsed(Boolean.TRUE, "true");
-        assertParsed(Boolean.FALSE, "false");
-        assertParsed(JSONObject.NULL, "null");
-        assertParsed(JSONObject.NULL, "NULL");
-        assertParsed(Boolean.FALSE, "False");
-        assertParsed(Boolean.TRUE, "truE");
-    }
-
-    public void testParsingQuotedStrings() throws JSONException {
-        assertParsed("abc", "\"abc\"");
-        assertParsed("123", "\"123\"");
-        assertParsed("foo\nbar", "\"foo\\nbar\"");
-        assertParsed("foo bar", "\"foo\\u0020bar\"");
-        assertParsed("\"{}[]/\\:,=;#", "\"\\\"{}[]/\\\\:,=;#\"");
-    }
-
-    public void testParsingSingleQuotedStrings() throws JSONException {
-        assertParsed("abc", "'abc'");
-        assertParsed("123", "'123'");
-        assertParsed("foo\nbar", "'foo\\nbar'");
-        assertParsed("foo bar", "'foo\\u0020bar'");
-        assertParsed("\"{}[]/\\:,=;#", "'\\\"{}[]/\\\\:,=;#'");
-    }
-
-    public void testParsingUnquotedStrings() throws JSONException {
-        assertParsed("abc", "abc");
-        assertParsed("123abc", "123abc");
-        assertParsed("123e0x", "123e0x");
-        assertParsed("123e", "123e");
-        assertParsed("123ee21", "123ee21");
-        assertParsed("0xFFFFFFFFFFFFFFFFF", "0xFFFFFFFFFFFFFFFFF");
-    }
-
-    /**
-     * Unfortunately the original implementation attempts to figure out what
-     * Java number type best suits an input value.
-     */
-    public void testParsingNumbersThatAreBestRepresentedAsLongs() throws JSONException {
-        assertParsed(9223372036854775807L, "9223372036854775807");
-        assertParsed(9223372036854775806L, "9223372036854775806");
-        assertParsed(-9223372036854775808L, "-9223372036854775808");
-        assertParsed(-9223372036854775807L, "-9223372036854775807");
-    }
-
-    public void testParsingNumbersThatAreBestRepresentedAsIntegers() throws JSONException {
-        assertParsed(0, "0");
-        assertParsed(5, "5");
-        assertParsed(-2147483648, "-2147483648");
-        assertParsed(2147483647, "2147483647");
-    }
-
-    public void testParsingNegativeZero() throws JSONException {
-        assertParsed(0, "-0");
-    }
-
-    public void testParsingIntegersWithAdditionalPrecisionYieldDoubles() throws JSONException {
-        assertParsed(1d, "1.00");
-        assertParsed(1d, "1.0");
-        assertParsed(0d, "0.0");
-        assertParsed(-0d, "-0.0");
-    }
-
-    public void testParsingNumbersThatAreBestRepresentedAsDoubles() throws JSONException {
-        assertParsed(9.223372036854776E18, "9223372036854775808");
-        assertParsed(-9.223372036854776E18, "-9223372036854775809");
-        assertParsed(1.7976931348623157E308, "1.7976931348623157e308");
-        assertParsed(2.2250738585072014E-308, "2.2250738585072014E-308");
-        assertParsed(4.9E-324, "4.9E-324");
-        assertParsed(4.9E-324, "4.9e-324");
-    }
-
-    public void testParsingOctalNumbers() throws JSONException {
-        assertParsed(5, "05");
-        assertParsed(8, "010");
-        assertParsed(1046, "02026");
-    }
-
-    public void testParsingHexNumbers() throws JSONException {
-        assertParsed(5, "0x5");
-        assertParsed(16, "0x10");
-        assertParsed(8230, "0x2026");
-        assertParsed(180150010, "0xABCDEFA");
-        assertParsed(2077093803, "0x7BCDEFAB");
-    }
-
-    public void testParsingLargeHexValues() throws JSONException {
-        assertParsed(Integer.MAX_VALUE, "0x7FFFFFFF");
-        String message = "Hex values are parsed as Strings if their signed " +
-                "value is greater than Integer.MAX_VALUE.";
-        assertParsed(message, 0x80000000L, "0x80000000");
-    }
-
-    public void test64BitHexValues() throws JSONException {
-        assertParsed("Large hex longs shouldn't be yield ints or strings",
-                -1L, "0xFFFFFFFFFFFFFFFF");
-    }
-
-    public void testParsingWithCommentsAndWhitespace() throws JSONException {
-        assertParsed("baz", "  // foo bar \n baz");
-        assertParsed("baz", "  // foo bar \r baz");
-        assertParsed("baz", "  // foo bar \r\n baz");
-        assertParsed("baz", "  # foo bar \n baz");
-        assertParsed("baz", "  # foo bar \r baz");
-        assertParsed("baz", "  # foo bar \r\n baz");
-        assertParsed(5, "  /* foo bar \n baz */ 5");
-        assertParsed(5, "  /* foo bar \n baz */ 5 // quux");
-        assertParsed(5, "  5   ");
-        assertParsed(5, "  5  \r\n\t ");
-        assertParsed(5, "\r\n\t   5 ");
-    }
-
-    public void testParsingArrays() throws JSONException {
-        assertParsed(array(), "[]");
-        assertParsed(array(5, 6, true), "[5,6,true]");
-        assertParsed(array(5, 6, array()), "[5,6,[]]");
-        assertParsed(array(5, 6, 7), "[5;6;7]");
-        assertParsed(array(5, 6, 7), "[5  , 6 \t; \r\n 7\n]");
-        assertParsed(array(5, 6, 7, null), "[5,6,7,]");
-        assertParsed(array(null, null), "[,]");
-        assertParsed(array(5, null, null, null, 5), "[5,,,,5]");
-        assertParsed(array(null, 5), "[,5]");
-        assertParsed(array(null, null, null), "[,,]");
-        assertParsed(array(null, null, null, 5), "[,,,5]");
-    }
-
-    public void testParsingObjects() throws JSONException {
-        assertParsed(object("foo", 5), "{\"foo\": 5}");
-        assertParsed(object("foo", 5), "{foo: 5}");
-        assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\": 5, \"bar\": \"baz\"}");
-        assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\": 5; \"bar\": \"baz\"}");
-        assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\"= 5; \"bar\"= \"baz\"}");
-        assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\"=> 5; \"bar\"=> \"baz\"}");
-        assertParsed(object("foo", object(), "bar", array()), "{\"foo\"=> {}; \"bar\"=> []}");
-        assertParsed(object("foo", object("foo", array(5, 6))), "{\"foo\": {\"foo\": [5, 6]}}");
-        assertParsed(object("foo", object("foo", array(5, 6))), "{\"foo\":\n\t{\t \"foo\":[5,\r6]}}");
-    }
-
-    public void testSyntaxProblemUnterminatedObject() {
-        assertParseFail("{");
-        assertParseFail("{\"foo\"");
-        assertParseFail("{\"foo\":");
-        assertParseFail("{\"foo\":bar");
-        assertParseFail("{\"foo\":bar,");
-        assertParseFail("{\"foo\":bar,\"baz\"");
-        assertParseFail("{\"foo\":bar,\"baz\":");
-        assertParseFail("{\"foo\":bar,\"baz\":true");
-        assertParseFail("{\"foo\":bar,\"baz\":true,");
-    }
-
-    public void testSyntaxProblemEmptyString() {
-        assertParseFail("");
-    }
-
-    public void testSyntaxProblemUnterminatedArray() {
-        assertParseFail("[");
-        assertParseFail("[,");
-        assertParseFail("[,,");
-        assertParseFail("[true");
-        assertParseFail("[true,");
-        assertParseFail("[true,,");
-    }
-
-    public void testSyntaxProblemMalformedObject() {
-        assertParseFail("{:}");
-        assertParseFail("{\"key\":}");
-        assertParseFail("{:true}");
-        assertParseFail("{\"key\":true:}");
-        assertParseFail("{null:true}");
-        assertParseFail("{true:true}");
-        assertParseFail("{0xFF:true}");
-    }
-
-    private void assertParseFail(String malformedJson) {
-        try {
-            new JSONTokener(malformedJson).nextValue();
-            fail("Successfully parsed: \"" + malformedJson + "\"");
-        } catch (JSONException e) {
-        } catch (StackOverflowError e) {
-            fail("Stack overflowed on input: \"" + malformedJson + "\"");
-        }
-    }
-
-    private JSONArray array(Object... elements) {
-        return new JSONArray(Arrays.asList(elements));
-    }
-
-    private JSONObject object(Object... keyValuePairs) throws JSONException {
-        JSONObject result = new JSONObject();
-        for (int i = 0; i < keyValuePairs.length; i+=2) {
-            result.put((String) keyValuePairs[i], keyValuePairs[i+1]);
-        }
-        return result;
-    }
-
-    private void assertParsed(String message, Object expected, String json) throws JSONException {
-        Object actual = new JSONTokener(json).nextValue();
-        actual = canonicalize(actual);
-        expected = canonicalize(expected);
-        assertEquals("For input \"" + json + "\" " + message, expected, actual);
-    }
-
-    private void assertParsed(Object expected, String json) throws JSONException {
-        assertParsed("", expected, json);
-    }
-
-    /**
-     * Since they don't implement equals or hashCode properly, this recursively
-     * replaces JSONObjects with an equivalent HashMap, and JSONArrays with the
-     * equivalent ArrayList.
-     */
-    private Object canonicalize(Object input) throws JSONException {
-        if (input instanceof JSONArray) {
-            JSONArray array = (JSONArray) input;
-            List<Object> result = new ArrayList<Object>();
-            for (int i = 0; i < array.length(); i++) {
-                result.add(canonicalize(array.opt(i)));
-            }
-            return result;
-        } else if (input instanceof JSONObject) {
-            JSONObject object = (JSONObject) input;
-            Map<String, Object> result = new HashMap<String, Object>();
-            for (Iterator<?> i = object.keys(); i.hasNext(); ) {
-                String key = (String) i.next();
-                result.put(key, canonicalize(object.get(key)));
-            }
-            return result;
-        } else if (input == null || input.equals(JSONObject.NULL)) {
-            return JSONObject.NULL;
-        } else {
-            return input;
-        }
-    }
-}
diff --git a/org/json/SelfUseTest.java b/org/json/SelfUseTest.java
deleted file mode 100644
index 15a0824..0000000
--- a/org/json/SelfUseTest.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open 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 org.json;
-
-import junit.framework.TestCase;
-
-/**
- * These tests checks self use calls. For the most part we doesn't attempt to
- * cover self-use, except in those cases where our clean room implementation
- * does it.
- *
- * <p>This black box test was written without inspecting the non-free org.json
- * sourcecode.
- */
-public class SelfUseTest extends TestCase {
-
-    private int objectPutCalls = 0;
-    private int objectGetCalls = 0;
-    private int objectOptCalls = 0;
-    private int objectOptTypeCalls = 0;
-    private int arrayPutCalls = 0;
-    private int arrayGetCalls = 0;
-    private int arrayOptCalls = 0;
-    private int arrayOptTypeCalls = 0;
-    private int tokenerNextCalls = 0;
-    private int tokenerNextValueCalls = 0;
-
-    private final JSONObject object = new JSONObject() {
-        @Override public JSONObject put(String name, Object value) throws JSONException {
-            objectPutCalls++;
-            return super.put(name, value);
-        }
-        @Override public Object get(String name) throws JSONException {
-            objectGetCalls++;
-            return super.get(name);
-        }
-        @Override public Object opt(String name) {
-            objectOptCalls++;
-            return super.opt(name);
-        }
-        @Override public boolean optBoolean(String key, boolean defaultValue) {
-            objectOptTypeCalls++;
-            return super.optBoolean(key, defaultValue);
-        }
-        @Override public double optDouble(String key, double defaultValue) {
-            objectOptTypeCalls++;
-            return super.optDouble(key, defaultValue);
-        }
-        @Override public int optInt(String key, int defaultValue) {
-            objectOptTypeCalls++;
-            return super.optInt(key, defaultValue);
-        }
-        @Override public long optLong(String key, long defaultValue) {
-            objectOptTypeCalls++;
-            return super.optLong(key, defaultValue);
-        }
-        @Override public String optString(String key, String defaultValue) {
-            objectOptTypeCalls++;
-            return super.optString(key, defaultValue);
-        }
-    };
-
-    private final JSONArray array = new JSONArray() {
-        @Override public JSONArray put(int index, Object value) throws JSONException {
-            arrayPutCalls++;
-            return super.put(index, value);
-        }
-        @Override public Object get(int index) throws JSONException {
-            arrayGetCalls++;
-            return super.get(index);
-        }
-        @Override public Object opt(int index) {
-            arrayOptCalls++;
-            return super.opt(index);
-        }
-        @Override public boolean optBoolean(int index, boolean fallback) {
-            arrayOptTypeCalls++;
-            return super.optBoolean(index, fallback);
-        }
-        @Override public double optDouble(int index, double fallback) {
-            arrayOptTypeCalls++;
-            return super.optDouble(index, fallback);
-        }
-        @Override public long optLong(int index, long fallback) {
-            arrayOptTypeCalls++;
-            return super.optLong(index, fallback);
-        }
-        @Override public String optString(int index, String fallback) {
-            arrayOptTypeCalls++;
-            return super.optString(index, fallback);
-        }
-        @Override public int optInt(int index, int fallback) {
-            arrayOptTypeCalls++;
-            return super.optInt(index, fallback);
-        }
-    };
-
-    private final JSONTokener tokener = new JSONTokener("{\"foo\": [true]}") {
-        @Override public char next() {
-            tokenerNextCalls++;
-            return super.next();
-        }
-        @Override public Object nextValue() throws JSONException {
-            tokenerNextValueCalls++;
-            return super.nextValue();
-        }
-    };
-
-
-    public void testObjectPut() throws JSONException {
-        object.putOpt("foo", "bar");
-        assertEquals(1, objectPutCalls);
-    }
-
-    public void testObjectAccumulate() throws JSONException {
-        object.accumulate("foo", "bar");
-        assertEquals(1, objectPutCalls);
-    }
-
-    public void testObjectGetBoolean() throws JSONException {
-        object.put("foo", "true");
-        object.getBoolean("foo");
-        assertEquals(1, objectGetCalls);
-    }
-
-    public void testObjectOptType() throws JSONException {
-        object.optBoolean("foo");
-        assertEquals(1, objectOptCalls);
-        assertEquals(1, objectOptTypeCalls);
-        object.optDouble("foo");
-        assertEquals(2, objectOptCalls);
-        assertEquals(2, objectOptTypeCalls);
-        object.optInt("foo");
-        assertEquals(3, objectOptCalls);
-        assertEquals(3, objectOptTypeCalls);
-        object.optLong("foo");
-        assertEquals(4, objectOptCalls);
-        assertEquals(4, objectOptTypeCalls);
-        object.optString("foo");
-        assertEquals(5, objectOptCalls);
-        assertEquals(5, objectOptTypeCalls);
-    }
-
-    public void testToJSONArray() throws JSONException {
-        object.put("foo", 5);
-        object.put("bar", 10);
-        array.put("foo");
-        array.put("baz");
-        array.put("bar");
-        object.toJSONArray(array);
-        assertEquals(3, arrayOptCalls);
-        assertEquals(0, arrayOptTypeCalls);
-        assertEquals(3, objectOptCalls);
-        assertEquals(0, objectOptTypeCalls);
-    }
-
-    public void testPutAtIndex() throws JSONException {
-        array.put(10, false);
-        assertEquals(1, arrayPutCalls);
-    }
-
-    public void testIsNull() {
-        array.isNull(5);
-        assertEquals(1, arrayOptCalls);
-    }
-
-    public void testArrayGetType() throws JSONException {
-        array.put(true);
-        array.getBoolean(0);
-        assertEquals(1, arrayGetCalls);
-    }
-
-    public void testArrayOptType() throws JSONException {
-        array.optBoolean(3);
-        assertEquals(1, arrayOptCalls);
-        assertEquals(1, arrayOptTypeCalls);
-        array.optDouble(3);
-        assertEquals(2, arrayOptCalls);
-        assertEquals(2, arrayOptTypeCalls);
-        array.optInt(3);
-        assertEquals(3, arrayOptCalls);
-        assertEquals(3, arrayOptTypeCalls);
-        array.optLong(3);
-        assertEquals(4, arrayOptCalls);
-        assertEquals(4, arrayOptTypeCalls);
-        array.optString(3);
-        assertEquals(5, arrayOptCalls);
-        assertEquals(5, arrayOptTypeCalls);
-    }
-
-    public void testToJSONObject() throws JSONException {
-        array.put("foo");
-        array.put("baz");
-        array.put("bar");
-        JSONArray values = new JSONArray();
-        values.put(5.5d);
-        values.put(11d);
-        values.put(30);
-        values.toJSONObject(array);
-        assertEquals(3, arrayOptCalls);
-        assertEquals(0, arrayOptTypeCalls);
-    }
-
-    public void testNextExpecting() throws JSONException {
-        tokener.next('{');
-        assertEquals(1, tokenerNextCalls);
-        tokener.next('\"');
-        assertEquals(2, tokenerNextCalls);
-    }
-
-    public void testNextValue() throws JSONException {
-        tokener.nextValue();
-        assertEquals(4, tokenerNextValueCalls);
-    }
-}
diff --git a/tck/java/time/zone/TCKZoneRules.java b/tck/java/time/zone/TCKZoneRules.java
index 67a10c0..2781ca5 100644
--- a/tck/java/time/zone/TCKZoneRules.java
+++ b/tck/java/time/zone/TCKZoneRules.java
@@ -943,21 +943,23 @@
     }
 
     public void test_Apia_jumpForwardOverInternationalDateLine_P12_to_M12() {
-        // transition occurred at 1879-07-04T00:00+12:33:04
+        // Android-changed: 1879 changed to 1892 in this test due to 2017c IANA update. Upstream
+        // will probably do the same. See https://bugs.openjdk.java.net/browse/JDK-8190259
+        // transition occurred at 1892-07-04T00:00+12:33:04
         ZoneRules test = pacificApia();
-        Instant instantBefore = LocalDate.of(1879, 7, 2).atStartOfDay(ZoneOffset.UTC).toInstant();
+        Instant instantBefore = LocalDate.of(1892, 7, 2).atStartOfDay(ZoneOffset.UTC).toInstant();
         ZoneOffsetTransition trans = test.nextTransition(instantBefore);
-        assertEquals(trans.getDateTimeBefore(), LocalDateTime.of(1879, 7, 5, 0, 0));
-        assertEquals(trans.getDateTimeAfter(), LocalDateTime.of(1879, 7, 4, 0, 0));
+        assertEquals(trans.getDateTimeBefore(), LocalDateTime.of(1892, 7, 5, 0, 0));
+        assertEquals(trans.getDateTimeAfter(), LocalDateTime.of(1892, 7, 4, 0, 0));
         assertEquals(trans.isGap(), false);
         assertEquals(trans.isOverlap(), true);
         assertEquals(trans.isValidOffset(ZoneOffset.ofHoursMinutesSeconds(+12, 33, 4)), true);
         assertEquals(trans.isValidOffset(ZoneOffset.ofHoursMinutesSeconds(-11, -26, -56)), true);
         assertEquals(trans.getDuration(), Duration.ofHours(-24));
-        assertEquals(trans.getInstant(), LocalDateTime.of(1879, 7, 4, 0, 0).toInstant(ZoneOffset.ofHoursMinutesSeconds(-11, -26, -56)));
+        assertEquals(trans.getInstant(), LocalDateTime.of(1892, 7, 4, 0, 0).toInstant(ZoneOffset.ofHoursMinutesSeconds(-11, -26, -56)));
 
-        ZonedDateTime zdt = ZonedDateTime.of(1879, 7, 4, 23, 0, 0, 0, ZoneId.of("Pacific/Apia"));
-        assertEquals(zdt.plusHours(2).toLocalDateTime(), LocalDateTime.of(1879, 7, 4, 1, 0, 0));
+        ZonedDateTime zdt = ZonedDateTime.of(1892, 7, 4, 23, 0, 0, 0, ZoneId.of("Pacific/Apia"));
+        assertEquals(zdt.plusHours(2).toLocalDateTime(), LocalDateTime.of(1892, 7, 4, 1, 0, 0));
     }
 
     //-------------------------------------------------------------------------