Import Android SDK Platform P [4423826]

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

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: I45f7bdc9b9c1cdcba75386623ae5f3ead6db4da8
diff --git a/android/accounts/AccountManagerPerfTest.java b/android/accounts/AccountManagerPerfTest.java
new file mode 100644
index 0000000..b9411fa
--- /dev/null
+++ b/android/accounts/AccountManagerPerfTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accounts;
+
+import static junit.framework.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AccountManagerPerfTest {
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testGetAccounts() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getTargetContext();
+        if (context.checkSelfPermission(Manifest.permission.GET_ACCOUNTS)
+                != PackageManager.PERMISSION_GRANTED) {
+            fail("Missing required GET_ACCOUNTS permission");
+        }
+        AccountManager accountManager = AccountManager.get(context);
+        while (state.keepRunning()) {
+            accountManager.getAccounts();
+        }
+    }
+}
diff --git a/android/app/Activity.java b/android/app/Activity.java
index 85f73bb..9d331a0 100644
--- a/android/app/Activity.java
+++ b/android/app/Activity.java
@@ -5867,10 +5867,11 @@
     }
 
     /**
-     * Returns complete component name of this activity.
+     * Returns the complete component name of this activity.
      *
      * @return Returns the complete component name for this activity
      */
+    @Override
     public ComponentName getComponentName()
     {
         return mComponent;
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
index fc4c8d7..8d9dc1f 100644
--- a/android/app/ActivityManager.java
+++ b/android/app/ActivityManager.java
@@ -46,6 +46,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.BatteryStats;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -500,7 +501,7 @@
     public static final int PROCESS_STATE_SERVICE = 11;
 
     /** @hide Process is in the background running a receiver.   Note that from the
-     * perspective of oom_adj receivers run at a higher foreground level, but for our
+     * perspective of oom_adj, receivers run at a higher foreground level, but for our
      * prioritization here that is not necessary and putting them below services means
      * many fewer changes in some process states as they receive broadcasts. */
     public static final int PROCESS_STATE_RECEIVER = 12;
@@ -524,6 +525,20 @@
     /** @hide Process does not exist. */
     public static final int PROCESS_STATE_NONEXISTENT = 18;
 
+    // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
+    // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
+    // be updated to correctly map between them.
+    /**
+     * Maps ActivityManager.PROCESS_STATE_ values to ActivityManagerProto.ProcessState enum.
+     *
+     * @param amInt a process state of the form ActivityManager.PROCESS_STATE_
+     * @return the value of the corresponding android.app.ActivityManagerProto's ProcessState enum.
+     * @hide
+     */
+    public static final int processStateAmToProto(int amInt) {
+        return amInt * 100;
+    }
+
     /** @hide The lowest process state number */
     public static final int MIN_PROCESS_STATE = PROCESS_STATE_PERSISTENT;
 
@@ -833,9 +848,8 @@
     /**
      * Returns true if this is a low-RAM device.  Exactly whether a device is low-RAM
      * is ultimately up to the device configuration, but currently it generally means
-     * something in the class of a 512MB device with about a 800x480 or less screen.
-     * This is mostly intended to be used by apps to determine whether they should turn
-     * off certain features that require more RAM.
+     * something with 1GB or less of RAM.  This is mostly intended to be used by apps
+     * to determine whether they should turn off certain features that require more RAM.
      */
     public boolean isLowRamDevice() {
         return isLowRamDeviceStatic();
@@ -1596,6 +1610,9 @@
     public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
             throws SecurityException {
         try {
+            if (maxNum < 0) {
+                throw new IllegalArgumentException("The requested number of tasks should be >= 0");
+            }
             return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1882,7 +1899,7 @@
     public List<RunningTaskInfo> getRunningTasks(int maxNum)
             throws SecurityException {
         try {
-            return getService().getTasks(maxNum, 0);
+            return getService().getTasks(maxNum);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -3882,21 +3899,36 @@
         IBinder service = ServiceManager.checkService(name);
         if (service == null) {
             pw.println("  (Service not found)");
+            pw.flush();
             return;
         }
-        TransferPipe tp = null;
-        try {
-            pw.flush();
-            tp = new TransferPipe();
-            tp.setBufferPrefix("  ");
-            service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
-            tp.go(fd, 10000);
-        } catch (Throwable e) {
-            if (tp != null) {
-                tp.kill();
+        pw.flush();
+        if (service instanceof Binder) {
+            // If this is a local object, it doesn't make sense to do an async dump with it,
+            // just directly dump.
+            try {
+                service.dump(fd, args);
+            } catch (Throwable e) {
+                pw.println("Failure dumping service:");
+                e.printStackTrace(pw);
+                pw.flush();
             }
-            pw.println("Failure dumping service:");
-            e.printStackTrace(pw);
+        } else {
+            // Otherwise, it is remote, do the dump asynchronously to avoid blocking.
+            TransferPipe tp = null;
+            try {
+                pw.flush();
+                tp = new TransferPipe();
+                tp.setBufferPrefix("  ");
+                service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
+                tp.go(fd, 10000);
+            } catch (Throwable e) {
+                if (tp != null) {
+                    tp.kill();
+                }
+                pw.println("Failure dumping service:");
+                e.printStackTrace(pw);
+            }
         }
     }
 
diff --git a/android/app/AlarmManager.java b/android/app/AlarmManager.java
index 2813e8b..55f9e28 100644
--- a/android/app/AlarmManager.java
+++ b/android/app/AlarmManager.java
@@ -33,6 +33,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import libcore.util.ZoneInfoDB;
 
@@ -48,7 +49,7 @@
  * if it is not already running.  Registered alarms are retained while the
  * device is asleep (and can optionally wake the device up if they go off
  * during that time), but will be cleared if it is turned off and rebooted.
- * 
+ *
  * <p>The Alarm Manager holds a CPU wake lock as long as the alarm receiver's
  * onReceive() method is executing. This guarantees that the phone will not sleep
  * until you have finished handling the broadcast. Once onReceive() returns, the
@@ -296,7 +297,7 @@
      * {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates
      * how many past alarm events have been accumulated into this intent
      * broadcast.  Recurring alarms that have gone undelivered because the
-     * phone was asleep may have a count greater than one when delivered.  
+     * phone was asleep may have a count greater than one when delivered.
      *
      * <div class="note">
      * <p>
@@ -396,10 +397,10 @@
      * set a recurring alarm for the top of every hour but the phone was asleep
      * from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens,
      * then the next alarm will be sent at 9:00.
-     * 
-     * <p>If your application wants to allow the delivery times to drift in 
+     *
+     * <p>If your application wants to allow the delivery times to drift in
      * order to guarantee that at least a certain time interval always elapses
-     * between alarms, then the approach to take is to use one-time alarms, 
+     * between alarms, then the approach to take is to use one-time alarms,
      * scheduling the next one yourself when handling each alarm delivery.
      *
      * <p class="note">
@@ -1056,7 +1057,7 @@
         /**
          * Creates a new alarm clock description.
          *
-         * @param triggerTime time at which the underlying alarm is triggered in wall time 
+         * @param triggerTime time at which the underlying alarm is triggered in wall time
          *                    milliseconds since the epoch
          * @param showIntent an intent that can be used to show or edit details of
          *                        the alarm clock.
@@ -1089,7 +1090,7 @@
          * Returns an intent that can be used to show or edit details of the alarm clock in
          * the application that scheduled it.
          *
-         * <p class="note">Beware that any application can retrieve and send this intent, 
+         * <p class="note">Beware that any application can retrieve and send this intent,
          * potentially with additional fields filled in. See
          * {@link PendingIntent#send(android.content.Context, int, android.content.Intent)
          * PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()}
@@ -1121,5 +1122,13 @@
                 return new AlarmClockInfo[size];
             }
         };
+
+        /** @hide */
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime);
+            mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT);
+            proto.end(token);
+        }
     }
 }
diff --git a/android/app/Notification.java b/android/app/Notification.java
index fee7d6c..8226e0f 100644
--- a/android/app/Notification.java
+++ b/android/app/Notification.java
@@ -858,7 +858,7 @@
      *
      * @hide
      */
-    static public IBinder whitelistToken;
+    private IBinder mWhitelistToken;
 
     /**
      * Must be set by a process to start associating tokens with Notification objects
@@ -1876,12 +1876,12 @@
     {
         int version = parcel.readInt();
 
-        whitelistToken = parcel.readStrongBinder();
-        if (whitelistToken == null) {
-            whitelistToken = processWhitelistToken;
+        mWhitelistToken = parcel.readStrongBinder();
+        if (mWhitelistToken == null) {
+            mWhitelistToken = processWhitelistToken;
         }
         // Propagate this token to all pending intents that are unmarshalled from the parcel.
-        parcel.setClassCookie(PendingIntent.class, whitelistToken);
+        parcel.setClassCookie(PendingIntent.class, mWhitelistToken);
 
         when = parcel.readLong();
         creationTime = parcel.readLong();
@@ -1989,7 +1989,7 @@
      * @hide
      */
     public void cloneInto(Notification that, boolean heavy) {
-        that.whitelistToken = this.whitelistToken;
+        that.mWhitelistToken = this.mWhitelistToken;
         that.when = this.when;
         that.creationTime = this.creationTime;
         that.mSmallIcon = this.mSmallIcon;
@@ -2219,7 +2219,7 @@
     private void writeToParcelImpl(Parcel parcel, int flags) {
         parcel.writeInt(1);
 
-        parcel.writeStrongBinder(whitelistToken);
+        parcel.writeStrongBinder(mWhitelistToken);
         parcel.writeLong(when);
         parcel.writeLong(creationTime);
         if (mSmallIcon == null && icon != 0) {
@@ -4981,6 +4981,8 @@
                 mN.flags |= FLAG_SHOW_LIGHTS;
             }
 
+            mN.allPendingIntents = null;
+
             return mN;
         }
 
diff --git a/android/app/PendingIntent.java b/android/app/PendingIntent.java
index a25c226..8b76cc7 100644
--- a/android/app/PendingIntent.java
+++ b/android/app/PendingIntent.java
@@ -33,6 +33,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.AndroidException;
+import android.util.proto.ProtoOutputStream;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1081,7 +1082,16 @@
         sb.append('}');
         return sb.toString();
     }
-    
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        if (mTarget != null) {
+            proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString());
+        }
+        proto.end(token);
+    }
+
     public int describeContents() {
         return 0;
     }
@@ -1119,8 +1129,13 @@
      */
     public static void writePendingIntentOrNullToParcel(@Nullable PendingIntent sender,
             @NonNull Parcel out) {
-        out.writeStrongBinder(sender != null ? sender.mTarget.asBinder()
-                : null);
+        out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null);
+        if (sender != null) {
+            OnMarshaledListener listener = sOnMarshaledListener.get();
+            if (listener != null) {
+                listener.onMarshaled(sender, out, 0 /* flags */);
+            }
+        }
     }
 
     /**
diff --git a/android/app/WindowConfiguration.java b/android/app/WindowConfiguration.java
index 251863c..de27b4f 100644
--- a/android/app/WindowConfiguration.java
+++ b/android/app/WindowConfiguration.java
@@ -102,7 +102,7 @@
     public static final int ACTIVITY_TYPE_STANDARD = 1;
     /** Home/Launcher activity type. */
     public static final int ACTIVITY_TYPE_HOME = 2;
-    /** Recents/Overview activity type. */
+    /** Recents/Overview activity type. There is only one activity with this type in the system. */
     public static final int ACTIVITY_TYPE_RECENTS = 3;
     /** Assistant activity type. */
     public static final int ACTIVITY_TYPE_ASSISTANT = 4;
diff --git a/android/app/admin/DevicePolicyManager.java b/android/app/admin/DevicePolicyManager.java
index ab8edee..772c6d6 100644
--- a/android/app/admin/DevicePolicyManager.java
+++ b/android/app/admin/DevicePolicyManager.java
@@ -1542,6 +1542,92 @@
     public @interface ProvisioningPreCondition {}
 
     /**
+     * Disable all configurable SystemUI features during LockTask mode. This includes,
+     * <ul>
+     *     <li>system info area in the status bar (connectivity icons, clock, etc.)
+     *     <li>notifications (including alerts, icons, and the notification shade)
+     *     <li>Home button
+     *     <li>Recents button and UI
+     *     <li>global actions menu (i.e. power button menu)
+     *     <li>keyguard
+     * </ul>
+     *
+     * This is the default configuration for LockTask.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_NONE = 0;
+
+    /**
+     * Enable the system info area in the status bar during LockTask mode. The system info area
+     * usually occupies the right side of the status bar (although this can differ across OEMs). It
+     * includes all system information indicators, such as date and time, connectivity, battery,
+     * vibration mode, etc.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1;
+
+    /**
+     * Enable notifications during LockTask mode. This includes notification icons on the status
+     * bar, heads-up notifications, and the expandable notification shade. Note that the Quick
+     * Settings panel will still be disabled.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 1 << 1;
+
+    /**
+     * Enable the Home button during LockTask mode. Note that if a custom launcher is used, it has
+     * to be registered as the default launcher with
+     * {@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}, and its
+     * package needs to be whitelisted for LockTask with
+     * {@link #setLockTaskPackages(ComponentName, String[])}.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_HOME = 1 << 2;
+
+    /**
+     * Enable the Recents button and the Recents screen during LockTask mode.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_RECENTS = 1 << 3;
+
+    /**
+     * Enable the global actions dialog during LockTask mode. This is the dialog that shows up when
+     * the user long-presses the power button, for example. Note that the user may not be able to
+     * power off the device if this flag is not set.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 1 << 4;
+
+    /**
+     * Enable the keyguard during LockTask mode. Note that if the keyguard is already disabled with
+     * {@link #setKeyguardDisabled(ComponentName, boolean)}, setting this flag will have no effect.
+     * If this flag is not set, the keyguard will not be shown even if the user has a lock screen
+     * credential.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_KEYGUARD = 1 << 5;
+
+    /**
+     * Flags supplied to {@link #setLockTaskFeatures(ComponentName, int)}.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true,
+            value = {LOCK_TASK_FEATURE_NONE, LOCK_TASK_FEATURE_SYSTEM_INFO,
+                    LOCK_TASK_FEATURE_NOTIFICATIONS, LOCK_TASK_FEATURE_HOME,
+                    LOCK_TASK_FEATURE_RECENTS, LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+                    LOCK_TASK_FEATURE_KEYGUARD})
+    public @interface LockTaskFeature {}
+
+    /**
      * Service action: Action for a service that device owner and profile owner can optionally
      * own.  If a device owner or a profile owner has such a service, the system tries to keep
      * a bound connection to it, in order to keep their process always running.
@@ -6484,6 +6570,61 @@
     }
 
     /**
+     * Sets which system features to enable for LockTask mode.
+     * <p>
+     * Feature flags set through this method will only take effect for the duration when the device
+     * is in LockTask mode. If this method is not called, none of the features listed here will be
+     * enabled.
+     * <p>
+     * This function can only be called by the device owner or by a profile owner of a user/profile
+     * that is affiliated with the device owner user. See {@link #setAffiliationIds}. Any features
+     * set via this method will be cleared if the user becomes unaffiliated.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param flags Bitfield of feature flags:
+     *              {@link #LOCK_TASK_FEATURE_NONE} (default),
+     *              {@link #LOCK_TASK_FEATURE_SYSTEM_INFO},
+     *              {@link #LOCK_TASK_FEATURE_NOTIFICATIONS},
+     *              {@link #LOCK_TASK_FEATURE_HOME},
+     *              {@link #LOCK_TASK_FEATURE_RECENTS},
+     *              {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
+     *              {@link #LOCK_TASK_FEATURE_KEYGUARD}
+     * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
+     * an affiliated user or profile.
+     */
+    public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) {
+        throwIfParentInstance("setLockTaskFeatures");
+        if (mService != null) {
+            try {
+                mService.setLockTaskFeatures(admin, flags);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Gets which system features are enabled for LockTask mode.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list.
+     * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
+     * an affiliated user or profile.
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public @LockTaskFeature int getLockTaskFeatures(@NonNull ComponentName admin) {
+        throwIfParentInstance("getLockTaskFeatures");
+        if (mService != null) {
+            try {
+                return mService.getLockTaskFeatures(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return 0;
+    }
+
+    /**
      * Called by device owners to update {@link android.provider.Settings.Global} settings.
      * Validation that the value of the setting is in the correct form for the setting type should
      * be performed by the caller.
@@ -6901,6 +7042,12 @@
      * Called by device owner to disable the status bar. Disabling the status bar blocks
      * notifications, quick settings and other screen overlays that allow escaping from a single use
      * device.
+     * <p>
+     * <strong>Note:</strong> This method has no effect for LockTask mode. The behavior of the
+     * status bar in LockTask mode can be configured with
+     * {@link #setLockTaskFeatures(ComponentName, int)}. Calls to this method when the device is in
+     * LockTask mode will be registered, but will only take effect when the device leaves LockTask
+     * mode.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param disabled {@code true} disables the status bar, {@code false} reenables it.
diff --git a/android/app/job/JobInfo.java b/android/app/job/JobInfo.java
index 1434c9b..b640bd5 100644
--- a/android/app/job/JobInfo.java
+++ b/android/app/job/JobInfo.java
@@ -18,6 +18,7 @@
 
 import static android.util.TimeUtils.formatDuration;
 
+import android.annotation.BytesLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -71,6 +72,9 @@
     /** This job requires metered connectivity such as most cellular data networks. */
     public static final int NETWORK_TYPE_METERED = 4;
 
+    /** Sentinel value indicating that bytes are unknown. */
+    public static final int NETWORK_BYTES_UNKNOWN = -1;
+
     /**
      * Amount of backoff a job has initially by default, in milliseconds.
      */
@@ -250,6 +254,7 @@
     private final boolean hasEarlyConstraint;
     private final boolean hasLateConstraint;
     private final int networkType;
+    private final long networkBytes;
     private final long minLatencyMillis;
     private final long maxExecutionDelayMillis;
     private final boolean isPeriodic;
@@ -387,6 +392,18 @@
     }
 
     /**
+     * Return the estimated size of network traffic that will be performed by
+     * this job, in bytes.
+     *
+     * @return Estimated size of network traffic, or
+     *         {@link #NETWORK_BYTES_UNKNOWN} when unknown.
+     * @see Builder#setEstimatedNetworkBytes(long)
+     */
+    public @BytesLong long getEstimatedNetworkBytes() {
+        return networkBytes;
+    }
+
+    /**
      * Set for a job that does not recur periodically, to specify a delay after which the job
      * will be eligible for execution. This value is not set if the job recurs periodically.
      */
@@ -524,6 +541,9 @@
         if (networkType != j.networkType) {
             return false;
         }
+        if (networkBytes != j.networkBytes) {
+            return false;
+        }
         if (minLatencyMillis != j.minLatencyMillis) {
             return false;
         }
@@ -582,6 +602,7 @@
         hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
         hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
         hashCode = 31 * hashCode + networkType;
+        hashCode = 31 * hashCode + Long.hashCode(networkBytes);
         hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
         hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
         hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic);
@@ -612,6 +633,7 @@
         triggerContentUpdateDelay = in.readLong();
         triggerContentMaxDelay = in.readLong();
         networkType = in.readInt();
+        networkBytes = in.readLong();
         minLatencyMillis = in.readLong();
         maxExecutionDelayMillis = in.readLong();
         isPeriodic = in.readInt() == 1;
@@ -640,6 +662,7 @@
         triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
         triggerContentMaxDelay = b.mTriggerContentMaxDelay;
         networkType = b.mNetworkType;
+        networkBytes = b.mNetworkBytes;
         minLatencyMillis = b.mMinLatencyMillis;
         maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
         isPeriodic = b.mIsPeriodic;
@@ -677,6 +700,7 @@
         out.writeLong(triggerContentUpdateDelay);
         out.writeLong(triggerContentMaxDelay);
         out.writeInt(networkType);
+        out.writeLong(networkBytes);
         out.writeLong(minLatencyMillis);
         out.writeLong(maxExecutionDelayMillis);
         out.writeInt(isPeriodic ? 1 : 0);
@@ -810,6 +834,7 @@
         // Requirements.
         private int mConstraintFlags;
         private int mNetworkType;
+        private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
         private ArrayList<TriggerContentUri> mTriggerContentUris;
         private long mTriggerContentUpdateDelay = -1;
         private long mTriggerContentMaxDelay = -1;
@@ -909,12 +934,21 @@
         }
 
         /**
-         * 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 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.
+         * <p class="note">
+         * 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.
+         *
+         * @see JobParameters#getNetwork()
          */
         public Builder setRequiredNetworkType(@NetworkType int networkType) {
             mNetworkType = networkType;
@@ -922,6 +956,43 @@
         }
 
         /**
+         * Set the estimated size of network traffic that will be performed by
+         * this job, in bytes.
+         * <p>
+         * Apps are encouraged to provide values that are as accurate as
+         * possible, but when the exact size isn't available, an
+         * order-of-magnitude estimate can be provided instead. Here are some
+         * specific examples:
+         * <ul>
+         * <li>A job that is backing up a photo knows the exact size of that
+         * photo, so it should provide that size as the estimate.
+         * <li>A job that refreshes top news stories wouldn't know an exact
+         * size, but if the size is expected to be consistently around 100KB, it
+         * can provide that order-of-magnitude value as the estimate.
+         * <li>A job that synchronizes email could end up using an extreme range
+         * of data, from under 1KB when nothing has changed, to dozens of MB
+         * when there are new emails with attachments. Jobs that cannot provide
+         * reasonable estimates should leave this estimated value undefined.
+         * </ul>
+         * Note that the system may choose to delay jobs with large network
+         * usage estimates when the device has a poor network connection, in
+         * order to save battery.
+         *
+         * @param networkBytes The estimated size of network traffic that will
+         *            be performed by this job, in bytes. This value only
+         *            reflects the traffic that will be performed by the base
+         *            job; if you're using {@link JobWorkItem} then you also
+         *            need to define the network traffic used by each work item
+         *            when constructing them.
+         * @see JobInfo#getEstimatedNetworkBytes()
+         * @see JobWorkItem#JobWorkItem(android.content.Intent, long)
+         */
+        public Builder setEstimatedNetworkBytes(@BytesLong long networkBytes) {
+            mNetworkBytes = networkBytes;
+            return this;
+        }
+
+        /**
          * Specify that to run this job, the device must be charging (or be a
          * non-battery-powered device connected to permanent power, such as Android TV
          * devices). This defaults to {@code false}.
@@ -1147,6 +1218,11 @@
                 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) {
+                throw new IllegalArgumentException(
+                        "Can't provide estimated network usage without requiring a network");
+            }
             // Check that a deadline was not set on a periodic job.
             if (mIsPeriodic) {
                 if (mMaxExecutionDelayMillis != 0L) {
diff --git a/android/app/job/JobParameters.java b/android/app/job/JobParameters.java
index a6f6be2..5053dc6 100644
--- a/android/app/job/JobParameters.java
+++ b/android/app/job/JobParameters.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.job.IJobCallback;
 import android.content.ClipData;
+import android.net.Network;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -66,6 +67,7 @@
     private final boolean overrideDeadlineExpired;
     private final Uri[] mTriggeredContentUris;
     private final String[] mTriggeredContentAuthorities;
+    private final Network network;
 
     private int stopReason; // Default value of stopReason is REASON_CANCELED
 
@@ -73,7 +75,7 @@
     public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
             Bundle transientExtras, ClipData clipData, int clipGrantFlags,
             boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
-            String[] triggeredContentAuthorities) {
+            String[] triggeredContentAuthorities, Network network) {
         this.jobId = jobId;
         this.extras = extras;
         this.transientExtras = transientExtras;
@@ -83,6 +85,7 @@
         this.overrideDeadlineExpired = overrideDeadlineExpired;
         this.mTriggeredContentUris = triggeredContentUris;
         this.mTriggeredContentAuthorities = triggeredContentAuthorities;
+        this.network = network;
     }
 
     /**
@@ -171,6 +174,28 @@
     }
 
     /**
+     * Return the network that should be used to perform any network requests
+     * for this job.
+     * <p>
+     * Devices may have multiple active network connections simultaneously, or
+     * they may not have a default network route at all. To correctly handle all
+     * situations like this, your job should always use the network returned by
+     * this method instead of implicitly using the default network route.
+     * <p>
+     * Note that the system may relax the constraints you originally requested,
+     * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
+     * a metered network when there is a surplus of metered data available.
+     *
+     * @return the network that should be used to perform any network requests
+     *         for this job, or {@code null} if this job didn't set any required
+     *         network type.
+     * @see JobInfo.Builder#setRequiredNetworkType(int)
+     */
+    public @Nullable Network getNetwork() {
+        return network;
+    }
+
+    /**
      * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their
      * currently running job.  Calling this method when there is no more work available and all
      * previously dequeued work has been completed will result in the system taking care of
@@ -257,6 +282,11 @@
         overrideDeadlineExpired = in.readInt() == 1;
         mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
         mTriggeredContentAuthorities = in.createStringArray();
+        if (in.readInt() != 0) {
+            network = Network.CREATOR.createFromParcel(in);
+        } else {
+            network = null;
+        }
         stopReason = in.readInt();
     }
 
@@ -286,6 +316,12 @@
         dest.writeInt(overrideDeadlineExpired ? 1 : 0);
         dest.writeTypedArray(mTriggeredContentUris, flags);
         dest.writeStringArray(mTriggeredContentAuthorities);
+        if (network != null) {
+            dest.writeInt(1);
+            network.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
         dest.writeInt(stopReason);
     }
 
diff --git a/android/app/job/JobWorkItem.java b/android/app/job/JobWorkItem.java
index 0eb0450..1c46e8e 100644
--- a/android/app/job/JobWorkItem.java
+++ b/android/app/job/JobWorkItem.java
@@ -16,6 +16,7 @@
 
 package android.app.job;
 
+import android.annotation.BytesLong;
 import android.content.Intent;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -27,6 +28,7 @@
  */
 final public class JobWorkItem implements Parcelable {
     final Intent mIntent;
+    final long mNetworkBytes;
     int mDeliveryCount;
     int mWorkId;
     Object mGrants;
@@ -39,6 +41,22 @@
      */
     public JobWorkItem(Intent intent) {
         mIntent = intent;
+        mNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+    }
+
+    /**
+     * Create a new piece of work, which can be submitted to
+     * {@link JobScheduler#enqueue JobScheduler.enqueue}.
+     *
+     * @param intent The general Intent describing this work.
+     * @param networkBytes The estimated size of network traffic that will be
+     *            performed by this job work item, in bytes. See
+     *            {@link JobInfo.Builder#setEstimatedNetworkBytes(long)} for
+     *            details about how to estimate.
+     */
+    public JobWorkItem(Intent intent, @BytesLong long networkBytes) {
+        mIntent = intent;
+        mNetworkBytes = networkBytes;
     }
 
     /**
@@ -49,6 +67,17 @@
     }
 
     /**
+     * Return the estimated size of network traffic that will be performed by
+     * this job work item, in bytes.
+     *
+     * @return estimated size, or {@link JobInfo#NETWORK_BYTES_UNKNOWN} when
+     *         unknown.
+     */
+    public @BytesLong long getEstimatedNetworkBytes() {
+        return mNetworkBytes;
+    }
+
+    /**
      * Return the count of the number of times this work item has been delivered
      * to the job.  The value will be > 1 if it has been redelivered because the job
      * was stopped or crashed while it had previously been delivered but before the
@@ -99,6 +128,10 @@
         sb.append(mWorkId);
         sb.append(" intent=");
         sb.append(mIntent);
+        if (mNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+            sb.append(" networkBytes=");
+            sb.append(mNetworkBytes);
+        }
         if (mDeliveryCount != 0) {
             sb.append(" dcount=");
             sb.append(mDeliveryCount);
@@ -118,6 +151,7 @@
         } else {
             out.writeInt(0);
         }
+        out.writeLong(mNetworkBytes);
         out.writeInt(mDeliveryCount);
         out.writeInt(mWorkId);
     }
@@ -139,6 +173,7 @@
         } else {
             mIntent = null;
         }
+        mNetworkBytes = in.readLong();
         mDeliveryCount = in.readInt();
         mWorkId = in.readInt();
     }
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
index 7f9f74b..f6b6b86 100644
--- a/android/app/slice/Slice.java
+++ b/android/app/slice/Slice.java
@@ -154,25 +154,6 @@
         return Arrays.asList(mHints);
     }
 
-    /**
-     * @hide
-     */
-    public SliceItem getPrimaryIcon() {
-        for (SliceItem item : 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;
-    }
-
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeStringArray(mHints);
@@ -405,6 +386,9 @@
             final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
                     null, extras);
             Bundle.setDefusable(res, true);
+            if (res == null) {
+                return null;
+            }
             return res.getParcelable(SliceProvider.EXTRA_SLICE);
         } catch (RemoteException e) {
             // Arbitrary and not worth documenting, as Activity
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
index df87b45..33825b4 100644
--- a/android/app/slice/SliceProvider.java
+++ b/android/app/slice/SliceProvider.java
@@ -156,27 +156,34 @@
     }
 
     private Slice handleBindSlice(Uri sliceUri) {
-        Slice[] output = new Slice[1];
-        CountDownLatch latch = new CountDownLatch(1);
-        Handler mainHandler = new Handler(Looper.getMainLooper());
-        mainHandler.post(() -> {
-            ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
-            try {
-                StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
-                        .detectAll()
-                        .penaltyDeath()
-                        .build());
-                output[0] = onBindSlice(sliceUri);
-            } finally {
-                StrictMode.setThreadPolicy(oldPolicy);
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            return onBindSliceStrict(sliceUri);
+        } else {
+            CountDownLatch latch = new CountDownLatch(1);
+            Slice[] output = new Slice[1];
+            Handler.getMain().post(() -> {
+                output[0] = onBindSliceStrict(sliceUri);
                 latch.countDown();
+            });
+            try {
+                latch.await();
+                return output[0];
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
             }
-        });
+        }
+    }
+
+    private Slice onBindSliceStrict(Uri sliceUri) {
+        ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
         try {
-            latch.await();
-            return output[0];
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
+            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                    .detectAll()
+                    .penaltyDeath()
+                    .build());
+            return onBindSlice(sliceUri);
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
         }
     }
 }
diff --git a/android/app/slice/SliceQuery.java b/android/app/slice/SliceQuery.java
index d1fe2c9..9943c49 100644
--- a/android/app/slice/SliceQuery.java
+++ b/android/app/slice/SliceQuery.java
@@ -35,6 +35,27 @@
     /**
      * @hide
      */
+    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;
+    }
+
+    /**
+     * @hide
+     */
     public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
         SliceItem ret = null;
         while (ret == null && list.size() != 0) {
diff --git a/android/app/slice/views/SliceView.java b/android/app/slice/views/SliceView.java
deleted file mode 100644
index 32484fc..0000000
--- a/android/app/slice/views/SliceView.java
+++ /dev/null
@@ -1,251 +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.app.slice.views;
-
-import android.annotation.StringDef;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import java.util.List;
-
-/**
- * A view that can display a {@link Slice} in different {@link SliceMode}'s.
- *
- * @hide
- */
-public class SliceView extends LinearLayout {
-
-    private static final String TAG = "SliceView";
-
-    /**
-     * @hide
-     */
-    public abstract static class SliceModeView extends FrameLayout {
-
-        public SliceModeView(Context context) {
-            super(context);
-        }
-
-        /**
-         * @return the {@link SliceMode} of the slice being presented.
-         */
-        public abstract String getMode();
-
-        /**
-         * @param slice the slice to show in this view.
-         */
-        public abstract void setSlice(Slice slice);
-    }
-
-    /**
-     * @hide
-     */
-    @StringDef({
-            MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
-    })
-    public @interface SliceMode {}
-
-    /**
-     * Mode indicating this slice should be presented in small template format.
-     */
-    public static final String MODE_SMALL       = "SLICE_SMALL";
-    /**
-     * Mode indicating this slice should be presented in large template format.
-     */
-    public static final String MODE_LARGE       = "SLICE_LARGE";
-    /**
-     * Mode indicating this slice should be presented as an icon.
-     */
-    public static final String MODE_SHORTCUT    = "SLICE_ICON";
-
-    /**
-     * Will select the type of slice binding based on size of the View. TODO: Put in some info about
-     * that selection.
-     */
-    private static final String MODE_AUTO = "auto";
-
-    private String mMode = MODE_AUTO;
-    private SliceModeView mCurrentView;
-    private final ActionRow mActions;
-    private Slice mCurrentSlice;
-    private boolean mShowActions = true;
-
-    /**
-     * Simple constructor to create a slice view from code.
-     *
-     * @param context The context the view is running in.
-     */
-    public SliceView(Context context) {
-        super(context);
-        setOrientation(LinearLayout.VERTICAL);
-        mActions = new ActionRow(mContext, true);
-        mActions.setBackground(new ColorDrawable(0xffeeeeee));
-        mCurrentView = new LargeTemplateView(mContext);
-        addView(mCurrentView);
-        addView(mActions);
-    }
-
-    /**
-     * @hide
-     */
-    public void bindSlice(Intent intent) {
-        // TODO
-    }
-
-    /**
-     * Binds this view to the {@link Slice} associated with the provided {@link Uri}.
-     */
-    public void bindSlice(Uri sliceUri) {
-        validate(sliceUri);
-        Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
-        bindSlice(s);
-    }
-
-    /**
-     * Binds this view to the provided {@link Slice}.
-     */
-    public void bindSlice(Slice slice) {
-        mCurrentSlice = slice;
-        if (mCurrentSlice != null) {
-            reinflate();
-        }
-    }
-
-    /**
-     * Call to clean up the view.
-     */
-    public void unbindSlice() {
-        mCurrentSlice = null;
-    }
-
-    /**
-     * Set the {@link SliceMode} this view should present in.
-     */
-    public void setMode(@SliceMode String mode) {
-        setMode(mode, false /* animate */);
-    }
-
-    /**
-     * @hide
-     */
-    public void setMode(@SliceMode String mode, boolean animate) {
-        if (animate) {
-            Log.e(TAG, "Animation not supported yet");
-        }
-        mMode = mode;
-        reinflate();
-    }
-
-    /**
-     * @return the {@link SliceMode} this view is presenting in.
-     */
-    public @SliceMode String getMode() {
-        if (mMode.equals(MODE_AUTO)) {
-            return MODE_LARGE;
-        }
-        return mMode;
-    }
-
-    /**
-     * @hide
-     *
-     * Whether this view should show a row of actions with it.
-     */
-    public void setShowActionRow(boolean show) {
-        mShowActions = show;
-        reinflate();
-    }
-
-    private SliceModeView createView(String mode) {
-        switch (mode) {
-            case MODE_SHORTCUT:
-                return new ShortcutView(getContext());
-            case MODE_SMALL:
-                return new SmallTemplateView(getContext());
-        }
-        return new LargeTemplateView(getContext());
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        unbindSlice();
-    }
-
-    private void reinflate() {
-        if (mCurrentSlice == null) {
-            return;
-        }
-        // TODO: Smarter mapping here from one state to the next.
-        SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
-        List<SliceItem> items = mCurrentSlice.getItems();
-        SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
-                Slice.HINT_ACTIONS,
-                Slice.HINT_ALT);
-        String mode = getMode();
-        if (!mode.equals(mCurrentView.getMode())) {
-            removeAllViews();
-            mCurrentView = createView(mode);
-            addView(mCurrentView);
-            addView(mActions);
-        }
-        if (items.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.equals(MODE_SHORTCUT);
-        if (showActions) {
-            mActions.setActions(actionRow, color);
-            mActions.setVisibility(View.VISIBLE);
-        } else {
-            mActions.setVisibility(View.GONE);
-        }
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        // TODO -- may need to rethink for AGSA
-        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            requestDisallowInterceptTouchEvent(true);
-        }
-        return super.onInterceptTouchEvent(ev);
-    }
-
-    private static void validate(Uri sliceUri) {
-        if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
-            throw new RuntimeException("Invalid uri " + sliceUri);
-        }
-        if (sliceUri.getPathSegments().size() == 0) {
-            throw new RuntimeException("Invalid uri " + sliceUri);
-        }
-    }
-}
diff --git a/android/app/slice/views/ActionRow.java b/android/app/slice/widget/ActionRow.java
similarity index 99%
rename from android/app/slice/views/ActionRow.java
rename to android/app/slice/widget/ActionRow.java
index c7d99f7..c96e630 100644
--- a/android/app/slice/views/ActionRow.java
+++ b/android/app/slice/widget/ActionRow.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.slice.views;
+package android.app.slice.widget;
 
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
diff --git a/android/app/slice/views/GridView.java b/android/app/slice/widget/GridView.java
similarity index 98%
rename from android/app/slice/views/GridView.java
rename to android/app/slice/widget/GridView.java
index 6f30c50..67a3c67 100644
--- a/android/app/slice/views/GridView.java
+++ b/android/app/slice/widget/GridView.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package android.app.slice.views;
+package android.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.app.slice.views.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
 import android.content.Context;
 import android.graphics.Color;
 import android.util.AttributeSet;
diff --git a/android/app/slice/views/LargeSliceAdapter.java b/android/app/slice/widget/LargeSliceAdapter.java
similarity index 97%
rename from android/app/slice/views/LargeSliceAdapter.java
rename to android/app/slice/widget/LargeSliceAdapter.java
index 6794ff9..267fff6 100644
--- a/android/app/slice/views/LargeSliceAdapter.java
+++ b/android/app/slice/widget/LargeSliceAdapter.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package android.app.slice.views;
+package android.app.slice.widget;
 
 import android.app.slice.Slice;
 import android.app.slice.SliceItem;
 import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceViewHolder;
+import android.app.slice.widget.LargeSliceAdapter.SliceViewHolder;
 import android.content.Context;
 import android.util.ArrayMap;
 import android.view.LayoutInflater;
@@ -71,7 +71,7 @@
 
     @Override
     public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View v = inflateforType(viewType);
+        View v = inflateForType(viewType);
         v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
         return new SliceViewHolder(v);
     }
@@ -104,7 +104,7 @@
         }
     }
 
-    private View inflateforType(int viewType) {
+    private View inflateForType(int viewType) {
         switch (viewType) {
             case TYPE_REMOTE_VIEWS:
                 return new FrameLayout(mContext);
diff --git a/android/app/slice/views/LargeTemplateView.java b/android/app/slice/widget/LargeTemplateView.java
similarity index 89%
rename from android/app/slice/views/LargeTemplateView.java
rename to android/app/slice/widget/LargeTemplateView.java
index 9e22516..f45b2a8 100644
--- a/android/app/slice/views/LargeTemplateView.java
+++ b/android/app/slice/widget/LargeTemplateView.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package android.app.slice.views;
+package android.app.slice.widget;
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import android.app.slice.Slice;
 import android.app.slice.SliceItem;
 import android.app.slice.SliceQuery;
-import android.app.slice.views.SliceView.SliceModeView;
+import android.app.slice.widget.SliceView.SliceModeView;
 import android.content.Context;
 import android.util.TypedValue;
 
@@ -35,11 +35,13 @@
  * @hide
  */
 public class LargeTemplateView extends SliceModeView {
+
     private final LargeSliceAdapter mAdapter;
     private final RecyclerView mRecyclerView;
     private final int mDefaultHeight;
     private final int mMaxHeight;
     private Slice mSlice;
+    private boolean mIsScrollable;
 
     public LargeTemplateView(Context context) {
         super(context);
@@ -49,9 +51,6 @@
         mAdapter = new LargeSliceAdapter(context);
         mRecyclerView.setAdapter(mAdapter);
         addView(mRecyclerView);
-        int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300,
-                getResources().getDisplayMetrics());
-        setLayoutParams(new LayoutParams(width, WRAP_CONTENT));
         mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
                 getResources().getDisplayMetrics());
         mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
@@ -68,7 +67,7 @@
         mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         if (mRecyclerView.getMeasuredHeight() > mMaxHeight
-                || mSlice.hasHint(Slice.HINT_PARTIAL)) {
+                || (mSlice != null && mSlice.hasHint(Slice.HINT_PARTIAL))) {
             mRecyclerView.getLayoutParams().height = mDefaultHeight;
         } else {
             mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
@@ -112,4 +111,12 @@
         sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
         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/android/app/slice/views/MessageView.java b/android/app/slice/widget/MessageView.java
similarity index 96%
rename from android/app/slice/views/MessageView.java
rename to android/app/slice/widget/MessageView.java
index 77252bf..3124398 100644
--- a/android/app/slice/views/MessageView.java
+++ b/android/app/slice/widget/MessageView.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package android.app.slice.views;
+package android.app.slice.widget;
 
 import android.app.slice.Slice;
 import android.app.slice.SliceItem;
 import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
diff --git a/android/app/slice/views/RemoteInputView.java b/android/app/slice/widget/RemoteInputView.java
similarity index 99%
rename from android/app/slice/views/RemoteInputView.java
rename to android/app/slice/widget/RemoteInputView.java
index e53cb1e..6eff5af 100644
--- a/android/app/slice/views/RemoteInputView.java
+++ b/android/app/slice/widget/RemoteInputView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.slice.views;
+package android.app.slice.widget;
 
 import android.animation.Animator;
 import android.app.Notification;
diff --git a/android/app/slice/views/ShortcutView.java b/android/app/slice/widget/ShortcutView.java
similarity index 90%
rename from android/app/slice/views/ShortcutView.java
rename to android/app/slice/widget/ShortcutView.java
index b6790c7..0bca8ce 100644
--- a/android/app/slice/views/ShortcutView.java
+++ b/android/app/slice/widget/ShortcutView.java
@@ -14,21 +14,20 @@
  * limitations under the License.
  */
 
-package android.app.slice.views;
+package android.app.slice.widget;
 
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
 import android.app.slice.Slice;
 import android.app.slice.SliceItem;
 import android.app.slice.SliceQuery;
-import android.app.slice.views.SliceView.SliceModeView;
+import android.app.slice.widget.SliceView.SliceModeView;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Color;
 import android.graphics.drawable.ShapeDrawable;
 import android.graphics.drawable.shapes.OvalShape;
 import android.net.Uri;
-import android.view.ViewGroup;
 
 import com.android.internal.R;
 
@@ -46,17 +45,14 @@
 
     public ShortcutView(Context context) {
         super(context);
-        mLargeIconSize = getContext().getResources()
-                .getDimensionPixelSize(R.dimen.slice_shortcut_size);
         mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
-        setLayoutParams(new ViewGroup.LayoutParams(mLargeIconSize, mLargeIconSize));
     }
 
     @Override
     public void setSlice(Slice slice) {
         removeAllViews();
         SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
-        SliceItem iconItem = slice.getPrimaryIcon();
+        SliceItem iconItem = SliceQuery.getPrimaryIcon(slice);
         SliceItem textItem = sliceItem != null
                 ? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT)
                 : SliceQuery.find(slice, SliceItem.TYPE_TEXT);
diff --git a/android/app/slice/widget/SliceView.java b/android/app/slice/widget/SliceView.java
new file mode 100644
index 0000000..5bafbc0
--- /dev/null
+++ b/android/app/slice/widget/SliceView.java
@@ -0,0 +1,402 @@
+/*
+ * 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.app.slice.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * 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>
+ * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content
+ * observer will be set for that uri and the view will update if there are any changes to the slice.
+ * To use this the app must have a special permission to bind to the slice (see
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ * <p>
+ * Example usage:
+ *
+ * <pre class="prettyprint">
+ * SliceView v = new SliceView(getContext());
+ * v.setMode(desiredMode);
+ * v.setSlice(sliceUri);
+ * </pre>
+ */
+public class SliceView extends ViewGroup {
+
+    private static final String TAG = "SliceView";
+
+    /**
+     * @hide
+     */
+    public abstract static class SliceModeView extends FrameLayout {
+
+        public SliceModeView(Context context) {
+            super(context);
+        }
+
+        /**
+         * @return the mode of the slice being presented.
+         */
+        public abstract String getMode();
+
+        /**
+         * @param slice the slice to show in this view.
+         */
+        public abstract void setSlice(Slice slice);
+    }
+
+    /**
+     * @hide
+     */
+    @StringDef({
+            MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+    })
+    public @interface SliceMode {}
+
+    /**
+     * Mode indicating this slice should be presented in small template format.
+     */
+    public static final String MODE_SMALL       = "SLICE_SMALL";
+    /**
+     * Mode indicating this slice should be presented in large template format.
+     */
+    public static final String MODE_LARGE       = "SLICE_LARGE";
+    /**
+     * Mode indicating this slice should be presented as an icon.
+     */
+    public static final String MODE_SHORTCUT    = "SLICE_ICON";
+
+    /**
+     * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+     * that selection.
+     */
+    private static final String MODE_AUTO = "auto";
+
+    private String mMode = MODE_AUTO;
+    private SliceModeView mCurrentView;
+    private final ActionRow mActions;
+    private Slice mCurrentSlice;
+    private boolean mShowActions = true;
+    private boolean mIsScrollable;
+    private SliceObserver mObserver;
+    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);
+        mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+        mActions = new ActionRow(mContext, true);
+        mActions.setBackground(new ColorDrawable(0xffeeeeee));
+        mCurrentView = new LargeTemplateView(mContext);
+        addView(mCurrentView, getChildLp(mCurrentView));
+        addView(mActions, getChildLp(mActions));
+        mShortcutSize = getContext().getResources()
+                .getDimensionPixelSize(R.dimen.slice_shortcut_size);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        measureChildren(widthMeasureSpec, heightMeasureSpec);
+        int actionHeight = mActions.getVisibility() != View.GONE
+                ? mActions.getMeasuredHeight()
+                : 0;
+        int newHeightSpec = MeasureSpec.makeMeasureSpec(
+                mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        setMeasuredDimension(width, newHeightSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        mCurrentView.layout(l, t, l + mCurrentView.getMeasuredWidth(),
+                t + mCurrentView.getMeasuredHeight());
+        if (mActions.getVisibility() != View.GONE) {
+            mActions.layout(l, mCurrentView.getMeasuredHeight(), l + mActions.getMeasuredWidth(),
+                    mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void showSlice(Intent intent) {
+        // TODO
+    }
+
+    /**
+     * Populates this view with the {@link Slice} associated with the provided {@link Uri}. 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 when the slice identified by the provided URI 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 the a slice was found for the provided uri.
+     * @see #clearSlice
+     */
+    public boolean setSlice(@NonNull Uri sliceUri) {
+        Preconditions.checkNotNull(sliceUri,
+                "Uri cannot be null, to remove the slice use clearSlice()");
+        if (sliceUri == null) {
+            clearSlice();
+            return false;
+        }
+        validate(sliceUri);
+        Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
+        if (s != null) {
+            mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+            if (isAttachedToWindow()) {
+                registerSlice(sliceUri);
+            }
+            showSlice(s);
+        }
+        return s != null;
+    }
+
+    /**
+     * Populates this view to the provided {@link Slice}.
+     * <p>
+     * This does not register a content observer on the URI that the slice is backed by so it will
+     * not update if the content changes. To have the view update when the content changes use
+     * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require
+     * any special permissions.
+     */
+    public void showSlice(@NonNull Slice slice) {
+        Preconditions.checkNotNull(slice,
+                "Slice cannot be null, to remove the slice use clearSlice()");
+        clearSlice();
+        mCurrentSlice = slice;
+        reinflate();
+    }
+
+    /**
+     * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is
+     * done automatically during {@link #onDetachedFromWindow()}.
+     * <p>
+     * It is safe to call this method multiple times.
+     */
+    public void clearSlice() {
+        mCurrentSlice = null;
+        if (mObserver != null) {
+            getContext().getContentResolver().unregisterContentObserver(mObserver);
+            mObserver = null;
+        }
+    }
+
+    /**
+     * Set the mode this view should present in.
+     */
+    public void setMode(@SliceMode String 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
+     */
+    public void setMode(@SliceMode String 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 String getMode() {
+        if (mMode.equals(MODE_AUTO)) {
+            return MODE_LARGE;
+        }
+        return mMode;
+    }
+
+    /**
+     * @hide
+     *
+     * Whether this view should show a row of actions with it.
+     */
+    public void setShowActionRow(boolean show) {
+        mShowActions = show;
+        reinflate();
+    }
+
+    private SliceModeView createView(String mode) {
+        switch (mode) {
+            case MODE_SHORTCUT:
+                return new ShortcutView(getContext());
+            case MODE_SMALL:
+                return new SmallTemplateView(getContext());
+        }
+        return new LargeTemplateView(getContext());
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mObserver != null) {
+            getContext().getContentResolver().unregisterContentObserver(mObserver);
+            mObserver = null;
+        }
+    }
+
+    private void registerSlice(Uri sliceUri) {
+        if (sliceUri == null || mObserver == null) {
+            return;
+        }
+        mContext.getContentResolver().registerContentObserver(sliceUri,
+                false /* notifyForDescendants */, mObserver);
+    }
+
+    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,
+                Slice.HINT_ALT);
+        String mode = getMode();
+        if (!mode.equals(mCurrentView.getMode())) {
+            removeAllViews();
+            mCurrentView = createView(mode);
+            addView(mCurrentView, getChildLp(mCurrentView));
+            addView(mActions, getChildLp(mActions));
+        }
+        if (mode.equals(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.equals(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);
+        }
+    }
+
+    private class SliceObserver extends ContentObserver {
+        SliceObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            this.onChange(selfChange, null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            Slice s = Slice.bindSlice(mContext.getContentResolver(), uri);
+            mCurrentSlice = s;
+            reinflate();
+        }
+    }
+}
diff --git a/android/app/slice/views/SliceViewUtil.java b/android/app/slice/widget/SliceViewUtil.java
similarity index 99%
rename from android/app/slice/views/SliceViewUtil.java
rename to android/app/slice/widget/SliceViewUtil.java
index 19e8e7c..0366998 100644
--- a/android/app/slice/views/SliceViewUtil.java
+++ b/android/app/slice/widget/SliceViewUtil.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app.slice.views;
+package android.app.slice.widget;
 
 import android.annotation.ColorInt;
 import android.content.Context;
diff --git a/android/app/slice/views/SmallTemplateView.java b/android/app/slice/widget/SmallTemplateView.java
similarity index 97%
rename from android/app/slice/views/SmallTemplateView.java
rename to android/app/slice/widget/SmallTemplateView.java
index 42b2d21..1c4c5df 100644
--- a/android/app/slice/views/SmallTemplateView.java
+++ b/android/app/slice/widget/SmallTemplateView.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package android.app.slice.views;
+package android.app.slice.widget;
 
 import android.app.PendingIntent.CanceledException;
 import android.app.slice.Slice;
 import android.app.slice.SliceItem;
 import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
-import android.app.slice.views.SliceView.SliceModeView;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.SliceView.SliceModeView;
 import android.content.Context;
 import android.os.AsyncTask;
 import android.view.View;
diff --git a/android/app/usage/AppStandby.java b/android/app/usage/AppStandby.java
new file mode 100644
index 0000000..6f9fc2f
--- /dev/null
+++ b/android/app/usage/AppStandby.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.usage;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets
+ * that affect how frequently they can run in the background or perform other battery-consuming
+ * actions. Buckets will be assigned based on how frequently or when the system thinks the user
+ * is likely to use the app.
+ * @hide
+ */
+public class AppStandby {
+
+    /** The app was used very recently, currently in use or likely to be used very soon. */
+    public static final int STANDBY_BUCKET_ACTIVE = 0;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app was used recently and/or likely to be used in the next few hours  */
+    public static final int STANDBY_BUCKET_WORKING_SET = 3;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app was used in the last few days and/or likely to be used in the next few days */
+    public static final int STANDBY_BUCKET_FREQUENT = 6;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app has not be used for several days and/or is unlikely to be used for several days */
+    public static final int STANDBY_BUCKET_RARE = 9;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app has never been used. */
+    public static final int STANDBY_BUCKET_NEVER = 12;
+
+    /** Reason for bucketing -- default initial state */
+    public static final String REASON_DEFAULT = "default";
+
+    /** Reason for bucketing -- timeout */
+    public static final String REASON_TIMEOUT = "timeout";
+
+    /** Reason for bucketing -- usage */
+    public static final String REASON_USAGE = "usage";
+
+    /** Reason for bucketing -- forced by user / shell command */
+    public static final String REASON_FORCED = "forced";
+
+    /**
+     * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will
+     * be appended.
+     */
+    public static final String REASON_PREDICTED = "predicted";
+
+    @IntDef(flag = false, value = {
+            STANDBY_BUCKET_ACTIVE,
+            STANDBY_BUCKET_WORKING_SET,
+            STANDBY_BUCKET_FREQUENT,
+            STANDBY_BUCKET_RARE,
+            STANDBY_BUCKET_NEVER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StandbyBuckets {}
+}
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index fd579fc..c827432 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -19,6 +19,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.usage.AppStandby.StandbyBuckets;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.RemoteException;
@@ -247,6 +248,29 @@
     }
 
     /**
+     * @hide
+     */
+    public @StandbyBuckets int getAppStandbyBucket(String packageName) {
+        try {
+            return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(),
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+        }
+        return AppStandby.STANDBY_BUCKET_ACTIVE;
+    }
+
+    /**
+     * @hide
+     */
+    public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
+        try {
+            mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
+        } catch (RemoteException e) {
+            // Nothing to do
+        }
+    }
+
+    /**
      * {@hide}
      * Temporarily whitelist the specified app for a short duration. This is to allow an app
      * receiving a high priority message to be able to access the network and acquire wakelocks
diff --git a/android/appwidget/AppWidgetHostView.java b/android/appwidget/AppWidgetHostView.java
index dc9970a..ab0eb92 100644
--- a/android/appwidget/AppWidgetHostView.java
+++ b/android/appwidget/AppWidgetHostView.java
@@ -19,8 +19,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.graphics.Color;
@@ -66,11 +64,8 @@
 
     // When we're inflating the initialLayout for a AppWidget, we only allow
     // views that are allowed in RemoteViews.
-    static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
-        public boolean onLoadClass(Class clazz) {
-            return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
-        }
-    };
+    private static final LayoutInflater.Filter INFLATER_FILTER =
+            (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
 
     Context mContext;
     Context mRemoteContext;
@@ -136,13 +131,19 @@
         mAppWidgetId = appWidgetId;
         mInfo = info;
 
+        // We add padding to the AppWidgetHostView if necessary
+        Rect padding = getDefaultPadding();
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+
         // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
         // a widget, eg. for some widgets in safe mode.
         if (info != null) {
-            // We add padding to the AppWidgetHostView if necessary
-            Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null);
-            setPadding(padding.left, padding.top, padding.right, padding.bottom);
-            updateContentDescription(info);
+            String description = info.loadLabel(getContext().getPackageManager());
+            if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
+                description = Resources.getSystem().getString(
+                        com.android.internal.R.string.suspended_widget_accessibility, description);
+            }
+            setContentDescription(description);
         }
     }
 
@@ -164,23 +165,23 @@
      */
     public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
             Rect padding) {
-        PackageManager packageManager = context.getPackageManager();
-        ApplicationInfo appInfo;
+        ApplicationInfo appInfo = null;
+        try {
+            appInfo = context.getPackageManager().getApplicationInfo(component.getPackageName(), 0);
+        } catch (NameNotFoundException e) {
+            // if we can't find the package, ignore
+        }
+        return getDefaultPaddingForWidget(context, appInfo, padding);
+    }
 
+    private static Rect getDefaultPaddingForWidget(Context context, ApplicationInfo appInfo,
+            Rect padding) {
         if (padding == null) {
             padding = new Rect(0, 0, 0, 0);
         } else {
             padding.set(0, 0, 0, 0);
         }
-
-        try {
-            appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
-        } catch (NameNotFoundException e) {
-            // if we can't find the package, return 0 padding
-            return padding;
-        }
-
-        if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+        if (appInfo != null && appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
             Resources r = context.getResources();
             padding.left = r.getDimensionPixelSize(com.android.internal.
                     R.dimen.default_app_widget_padding_left);
@@ -194,6 +195,11 @@
         return padding;
     }
 
+    private Rect getDefaultPadding() {
+        return getDefaultPaddingForWidget(mContext,
+                mInfo == null ? null : mInfo.providerInfo.applicationInfo, null);
+    }
+
     public int getAppWidgetId() {
         return mAppWidgetId;
     }
@@ -284,10 +290,7 @@
             newOptions = new Bundle();
         }
 
-        Rect padding = new Rect();
-        if (mInfo != null) {
-            padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding);
-        }
+        Rect padding = getDefaultPadding();
         float density = getResources().getDisplayMetrics().density;
 
         int xPaddingDips = (int) ((padding.left + padding.right) / density);
@@ -361,7 +364,7 @@
      * initial layout.
      */
     void resetAppWidget(AppWidgetProviderInfo info) {
-        mInfo = info;
+        setAppWidget(mAppWidgetId, info);
         mViewMode = VIEW_MODE_NOINIT;
         updateAppWidget(null);
     }
@@ -433,7 +436,6 @@
         }
 
         applyContent(content, recycled, exception);
-        updateContentDescription(mInfo);
     }
 
     private void applyContent(View content, boolean recycled, Exception exception) {
@@ -460,27 +462,6 @@
         }
     }
 
-    private void updateContentDescription(AppWidgetProviderInfo info) {
-        if (info != null) {
-            LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
-            ApplicationInfo appInfo = null;
-            try {
-                appInfo = launcherApps.getApplicationInfo(
-                        info.provider.getPackageName(), 0, info.getProfile());
-            } catch (NameNotFoundException e) {
-                // ignore -- use null.
-            }
-            if (appInfo != null &&
-                    (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
-                setContentDescription(
-                        Resources.getSystem().getString(
-                        com.android.internal.R.string.suspended_widget_accessibility, info.label));
-            } else {
-                setContentDescription(info.label);
-            }
-        }
-    }
-
     private void inflateAsync(RemoteViews remoteViews) {
         // Prepare a local reference to the remote Context so we're ready to
         // inflate any requested LayoutParams.
@@ -614,7 +595,7 @@
                 LayoutInflater inflater = (LayoutInflater)
                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                 inflater = inflater.cloneInContext(theirContext);
-                inflater.setFilter(sInflaterFilter);
+                inflater.setFilter(INFLATER_FILTER);
                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
 
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index f135244..1ddcb1a 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,9 +1,136 @@
-//ComputableLiveData interface for tests
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package android.arch.lifecycle;
-import android.arch.lifecycle.LiveData;
+
+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)
 public abstract class ComputableLiveData<T> {
-    public ComputableLiveData(){}
-    abstract protected T compute();
-    public LiveData<T> getLiveData() {return null;}
-    public void invalidate() {}
+
+    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();
 }
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 3aea6ac..5b09c32 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,4 +1,410 @@
-//LiveData interface for tests
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package android.arch.lifecycle;
-public class LiveData<T> {
+
+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");
+        }
+    }
 }
diff --git a/android/arch/paging/ContiguousDataSource.java b/android/arch/paging/ContiguousDataSource.java
index be9da20..414c4ff 100644
--- a/android/arch/paging/ContiguousDataSource.java
+++ b/android/arch/paging/ContiguousDataSource.java
@@ -18,55 +18,44 @@
 
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
 
 import java.util.List;
 
-/** @hide */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
+abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
     @Override
     boolean isContiguous() {
         return true;
     }
 
-    void loadInitial(Key key, int pageSize, boolean enablePlaceholders,
-            PageResult.Receiver<Key, Value> receiver) {
-        NullPaddedList<Value> initial = loadInitial(key, pageSize, enablePlaceholders);
-        if (initial != null) {
-            receiver.onPageResult(new PageResult<>(
-                    PageResult.INIT,
-                    new Page<Key, Value>(initial.mList),
-                    initial.getLeadingNullCount(),
-                    initial.getTrailingNullCount(),
-                    initial.getPositionOffset()));
-        } else {
-            receiver.onPageResult(new PageResult<Key, Value>(
-                    PageResult.INIT, null, 0, 0, 0));
-        }
-    }
+    abstract void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders,
+            @NonNull PageResult.Receiver<Key, Value> receiver);
 
     void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
-            PageResult.Receiver<Key, Value> receiver) {
-        List<Value> list = loadAfter(currentEndIndex, currentEndItem, pageSize);
+            @NonNull PageResult.Receiver<Key, Value> receiver) {
+        if (!isInvalid()) {
+            List<Value> list = loadAfterImpl(currentEndIndex, currentEndItem, pageSize);
 
-        Page<Key, Value> page = list != null
-                ? new Page<Key, Value>(list) : null;
-
-        receiver.postOnPageResult(new PageResult<>(
-                PageResult.APPEND, page, 0, 0, 0));
+            if (list != null && !isInvalid()) {
+                receiver.postOnPageResult(new PageResult<>(
+                        PageResult.APPEND, new Page<Key, Value>(list), 0, 0, 0));
+                return;
+            }
+        }
+        receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.APPEND));
     }
 
     void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
-            PageResult.Receiver<Key, Value> receiver) {
-        List<Value> list = loadBefore(currentBeginIndex, currentBeginItem, pageSize);
+            @NonNull PageResult.Receiver<Key, Value> receiver) {
+        if (!isInvalid()) {
+            List<Value> list = loadBeforeImpl(currentBeginIndex, currentBeginItem, pageSize);
 
-        Page<Key, Value> page = list != null
-                ? new Page<Key, Value>(list) : null;
-
-        receiver.postOnPageResult(new PageResult<>(
-                PageResult.PREPEND, page, 0, 0, 0));
+            if (list != null && !isInvalid()) {
+                receiver.postOnPageResult(new PageResult<>(
+                        PageResult.PREPEND, new Page<Key, Value>(list), 0, 0, 0));
+                return;
+            }
+        }
+        receiver.postOnPageResult(new PageResult<Key, Value>(PageResult.PREPEND));
     }
 
     /**
@@ -84,44 +73,4 @@
     @Nullable
     abstract List<Value> loadBeforeImpl(int currentBeginIndex,
             @NonNull Value currentBeginItem, int pageSize);
-
-    /** @hide */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @WorkerThread
-    @Nullable
-    public abstract NullPaddedList<Value> loadInitial(
-            Key key, int initialLoadSize, boolean enablePlaceholders);
-
-    /** @hide */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @WorkerThread
-    @Nullable
-    public final List<Value> loadAfter(int currentEndIndex,
-            @NonNull Value currentEndItem, int pageSize) {
-        if (isInvalid()) {
-            return null;
-        }
-        List<Value> list = loadAfterImpl(currentEndIndex, currentEndItem, pageSize);
-        if (isInvalid()) {
-            return null;
-        }
-        return list;
-    }
-
-    /** @hide */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @WorkerThread
-    @Nullable
-    public final List<Value> loadBefore(int currentBeginIndex,
-            @NonNull Value currentBeginItem, int pageSize) {
-        if (isInvalid()) {
-            return null;
-        }
-        List<Value> list = loadBeforeImpl(currentBeginIndex, currentBeginItem, pageSize);
-        if (isInvalid()) {
-            return null;
-        }
-        return list;
-
-    }
 }
diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java
index 2a5cd42..cdff391 100644
--- a/android/arch/paging/ContiguousPagedList.java
+++ b/android/arch/paging/ContiguousPagedList.java
@@ -47,7 +47,9 @@
             });
         }
 
-        @MainThread
+        // Creation thread for initial synchronous load, otherwise main thread
+        // Safe to access main thread only state - no other thread has reference during construction
+        @AnyThread
         @Override
         public void onPageResult(@NonNull PageResult<K, V> pageResult) {
             if (pageResult.page == null) {
@@ -70,6 +72,17 @@
             } else if (pageResult.type == PageResult.PREPEND) {
                 mKeyedStorage.prependPage(page, ContiguousPagedList.this);
             }
+
+            if (mBoundaryCallback != null) {
+                boolean deferEmpty = mStorage.size() == 0;
+                boolean deferBegin = !deferEmpty
+                        && pageResult.type == PageResult.PREPEND
+                        && pageResult.page.items.size() == 0;
+                boolean deferEnd = !deferEmpty
+                        && pageResult.type == PageResult.APPEND
+                        && pageResult.page.items.size() == 0;
+                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
+            }
         }
     };
 
@@ -77,9 +90,11 @@
             @NonNull ContiguousDataSource<K, V> dataSource,
             @NonNull Executor mainThreadExecutor,
             @NonNull Executor backgroundThreadExecutor,
+            @Nullable BoundaryCallback<V> boundaryCallback,
             @NonNull Config config,
             final @Nullable K key) {
-        super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor, config);
+        super(new PagedStorage<K, V>(), mainThreadExecutor, backgroundThreadExecutor,
+                boundaryCallback, config);
         mDataSource = dataSource;
 
         // blocking init just triggers the initial load on the construction thread -
@@ -168,7 +183,7 @@
         final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
 
         // safe to access first item here - mStorage can't be empty if we're prepending
-        final V item = mStorage.getFirstContiguousItem();
+        final V item = mStorage.getFirstLoadedItem();
         mBackgroundThreadExecutor.execute(new Runnable() {
             @Override
             public void run() {
@@ -191,7 +206,7 @@
                 + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
 
         // safe to access first item here - mStorage can't be empty if we're appending
-        final V item = mStorage.getLastContiguousItem();
+        final V item = mStorage.getLastLoadedItem();
         mBackgroundThreadExecutor.execute(new Runnable() {
             @Override
             public void run() {
@@ -234,6 +249,8 @@
         // finally dispatch callbacks, after prepend may have already been scheduled
         notifyChanged(leadingNulls, changedCount);
         notifyInserted(0, addedCount);
+
+        offsetBoundaryAccessIndices(addedCount);
     }
 
     @MainThread
diff --git a/android/arch/paging/DataSource.java b/android/arch/paging/DataSource.java
index 524e570..ff44521 100644
--- a/android/arch/paging/DataSource.java
+++ b/android/arch/paging/DataSource.java
@@ -48,6 +48,10 @@
 @SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety
 public abstract class DataSource<Key, Value> {
 
+    public interface Factory<Key, Value> {
+        DataSource<Key, Value> create();
+    }
+
     // Since we currently rely on implementation details of two implementations,
     // prevent external subclassing, except through exposed subclasses
     DataSource() {
diff --git a/android/arch/paging/KeyedDataSource.java b/android/arch/paging/KeyedDataSource.java
index 0d45294..3214a4e 100644
--- a/android/arch/paging/KeyedDataSource.java
+++ b/android/arch/paging/KeyedDataSource.java
@@ -19,7 +19,6 @@
 import android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
 import android.support.annotation.WorkerThread;
 
 import java.util.ArrayList;
@@ -124,9 +123,33 @@
         return list;
     }
 
+
+    @Override
+    void loadInitial(Key key, int initialLoadSize, boolean enablePlaceholders,
+            @NonNull PageResult.Receiver<Key, Value> receiver) {
+
+        PageResult<Key, Value> pageResult =
+                loadInitialInternal(key, initialLoadSize, enablePlaceholders);
+        if (pageResult == null) {
+            // loading failed, return empty page
+            receiver.onPageResult(new PageResult<Key, Value>(PageResult.INIT));
+        } else {
+            receiver.onPageResult(pageResult);
+        }
+    }
+
+    /**
+     * Try initial load, and either return the successful initial load to the receiver,
+     * or null if unsuccessful.
+     */
     @Nullable
-    private NullPaddedList<Value> loadInitialInternal(
+    private PageResult<Key, Value> loadInitialInternal(
             @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
+        // check if invalid at beginning, and before returning a valid list
+        if (isInvalid()) {
+            return null;
+        }
+
         List<Value> list;
         if (key == null) {
             // no key, so load initial.
@@ -171,9 +194,14 @@
             }
         }
 
+        final Page<Key, Value> page = new Page<>(list);
+
         if (list.isEmpty()) {
-            // wasn't able to load any items, so publish an unpadded empty list.
-            return new NullPaddedList<>(0, Collections.<Value>emptyList());
+            if (isInvalid()) {
+                return null;
+            }
+            // wasn't able to load any items, but not invalid - return an empty page.
+            return new PageResult<>(PageResult.INIT, page, 0, 0, 0);
         }
 
         int itemsBefore = COUNT_UNDEFINED;
@@ -181,31 +209,21 @@
         if (enablePlaceholders) {
             itemsBefore = countItemsBefore(getKey(list.get(0)));
             itemsAfter = countItemsAfter(getKey(list.get(list.size() - 1)));
-            if (isInvalid()) {
-                return null;
-            }
         }
-        if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) {
-            return new NullPaddedList<>(0, list, 0);
-        } else {
-            return new NullPaddedList<>(itemsBefore, list, itemsAfter);
-        }
-    }
 
-    /** @hide */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @WorkerThread
-    @Override
-    public NullPaddedList<Value> loadInitial(
-            @Nullable Key key, int initialLoadSize, boolean enablePlaceholders) {
         if (isInvalid()) {
             return null;
         }
-        NullPaddedList<Value> list = loadInitialInternal(key, initialLoadSize, enablePlaceholders);
-        if (list == null || isInvalid()) {
-            return null;
+        if (itemsBefore == COUNT_UNDEFINED || itemsAfter == COUNT_UNDEFINED) {
+            itemsBefore = 0;
+            itemsAfter = 0;
         }
-        return list;
+        return new PageResult<>(
+                PageResult.INIT,
+                page,
+                itemsBefore,
+                itemsAfter,
+                0);
     }
 
     /**
diff --git a/android/arch/paging/ListDataSource.java b/android/arch/paging/ListDataSource.java
new file mode 100644
index 0000000..d3a171e
--- /dev/null
+++ b/android/arch/paging/ListDataSource.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ListDataSource<T> extends TiledDataSource<T> {
+    private final List<T> mList;
+
+    public ListDataSource(List<T> list) {
+        mList = new ArrayList<>(list);
+    }
+
+    @Override
+    public int countItems() {
+        return mList.size();
+    }
+
+    @Override
+    public List<T> loadRange(int startPosition, int count) {
+        int endExclusive = Math.min(mList.size(), startPosition + count);
+        return mList.subList(startPosition, endExclusive);
+    }
+}
diff --git a/android/arch/paging/LivePagedListBuilder.java b/android/arch/paging/LivePagedListBuilder.java
new file mode 100644
index 0000000..ee1810b
--- /dev/null
+++ b/android/arch/paging/LivePagedListBuilder.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 android.arch.paging;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.lifecycle.ComputableLiveData;
+import android.arch.lifecycle.LiveData;
+import android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+public class LivePagedListBuilder<Key, Value> {
+    private Key mInitialLoadKey;
+    private PagedList.Config mConfig;
+    private DataSource.Factory<Key, Value> mDataSourceFactory;
+    private PagedList.BoundaryCallback mBoundaryCallback;
+    private Executor mMainThreadExecutor;
+    private Executor mBackgroundThreadExecutor;
+
+    @SuppressWarnings("WeakerAccess")
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) {
+        mInitialLoadKey = key;
+        return this;
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setPagingConfig(@NonNull PagedList.Config config) {
+        mConfig = config;
+        return this;
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setPagingConfig(int pageSize) {
+        mConfig = new PagedList.Config.Builder().setPageSize(pageSize).build();
+        return this;
+    }
+
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setDataSourceFactory(
+            @NonNull DataSource.Factory<Key, Value> dataSourceFactory) {
+        mDataSourceFactory = dataSourceFactory;
+        return this;
+    }
+
+    @SuppressWarnings("unused")
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setBoundaryCallback(
+            @Nullable PagedList.BoundaryCallback<Value> boundaryCallback) {
+        mBoundaryCallback = boundaryCallback;
+        return this;
+    }
+
+    @SuppressWarnings("unused")
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setMainThreadExecutor(
+            @NonNull Executor mainThreadExecutor) {
+        mMainThreadExecutor = mainThreadExecutor;
+        return this;
+    }
+
+    @SuppressWarnings("unused")
+    @NonNull
+    public LivePagedListBuilder<Key, Value> setBackgroundThreadExecutor(
+            @NonNull Executor backgroundThreadExecutor) {
+        mBackgroundThreadExecutor = backgroundThreadExecutor;
+        return this;
+    }
+
+    @NonNull
+    public LiveData<PagedList<Value>> build() {
+        if (mConfig == null) {
+            throw new IllegalArgumentException("PagedList.Config must be provided");
+        }
+        if (mDataSourceFactory == null) {
+            throw new IllegalArgumentException("DataSource.Factory must be provided");
+        }
+        if (mMainThreadExecutor == null) {
+            mMainThreadExecutor = ArchTaskExecutor.getMainThreadExecutor();
+        }
+        if (mBackgroundThreadExecutor == null) {
+            mBackgroundThreadExecutor = ArchTaskExecutor.getIOThreadExecutor();
+        }
+
+        return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
+                mMainThreadExecutor, mBackgroundThreadExecutor);
+    }
+
+    @AnyThread
+    @NonNull
+    public static <Key, Value> LiveData<PagedList<Value>> create(
+            @Nullable final Key initialLoadKey,
+            @NonNull final PagedList.Config config,
+            @Nullable final PagedList.BoundaryCallback boundaryCallback,
+            @NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
+            @NonNull final Executor mainThreadExecutor,
+            @NonNull final Executor backgroundThreadExecutor) {
+        return new ComputableLiveData<PagedList<Value>>() {
+            @Nullable
+            private PagedList<Value> mList;
+            @Nullable
+            private DataSource<Key, Value> mDataSource;
+
+            private final DataSource.InvalidatedCallback mCallback =
+                    new DataSource.InvalidatedCallback() {
+                        @Override
+                        public void onInvalidated() {
+                            invalidate();
+                        }
+                    };
+
+            @Override
+            protected PagedList<Value> compute() {
+                @Nullable Key initializeKey = initialLoadKey;
+                if (mList != null) {
+                    //noinspection unchecked
+                    initializeKey = (Key) mList.getLastKey();
+                }
+
+                do {
+                    if (mDataSource != null) {
+                        mDataSource.removeInvalidatedCallback(mCallback);
+                    }
+
+                    mDataSource = dataSourceFactory.create();
+                    mDataSource.addInvalidatedCallback(mCallback);
+
+                    mList = new PagedList.Builder<Key, Value>()
+                            .setDataSource(mDataSource)
+                            .setMainThreadExecutor(mainThreadExecutor)
+                            .setBackgroundThreadExecutor(backgroundThreadExecutor)
+                            .setBoundaryCallback(boundaryCallback)
+                            .setConfig(config)
+                            .setInitialKey(initializeKey)
+                            .build();
+                } while (mList.isDetached());
+                return mList;
+            }
+        }.getLiveData();
+    }
+}
diff --git a/android/arch/paging/LivePagedListProvider.java b/android/arch/paging/LivePagedListProvider.java
index 07dd84b..b7c68dd 100644
--- a/android/arch/paging/LivePagedListProvider.java
+++ b/android/arch/paging/LivePagedListProvider.java
@@ -16,133 +16,5 @@
 
 package android.arch.paging;
 
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.lifecycle.ComputableLiveData;
-import android.arch.lifecycle.LiveData;
-import android.support.annotation.AnyThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.WorkerThread;
-
-/**
- * Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource.
- * <p>
- * Return type for data-loading system of an application or library to produce a
- * {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the
- * consumer.
- * <p>
- * If you're using Room, it can generate a LivePagedListProvider from a query:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- *     public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
- * }</pre>
- * In the above sample, {@code Integer} is used because it is the {@code Key} type of
- * {@link TiledDataSource}. Currently, Room can only generate a {@code LIMIT}/{@code OFFSET},
- * position based loader that uses TiledDataSource under the hood, and specifying {@code Integer}
- * here lets you pass an initial loading position as an integer.
- * <p>
- * In the future, Room plans to offer other key types to support paging content with a
- * {@link KeyedDataSource}.
- *
- * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
- *             you're using TiledDataSource.
- * @param <Value> Data type produced by the DataSource, and held by the PagedLists.
- *
- * @see PagedListAdapter
- * @see DataSource
- * @see PagedList
- */
-public abstract class LivePagedListProvider<Key, Value> {
-
-    /**
-     * Construct a new data source to be wrapped in a new PagedList, which will be returned
-     * through the LiveData.
-     *
-     * @return The data source.
-     */
-    @WorkerThread
-    protected abstract DataSource<Key, Value> createDataSource();
-
-    /**
-     * Creates a LiveData of PagedLists, given the page size.
-     * <p>
-     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
-     * {@link android.support.v7.widget.RecyclerView}.
-     *
-     * @param initialLoadKey Initial key used to load initial data from the data source.
-     * @param pageSize Page size defining how many items are loaded from a data source at a time.
-     *                 Recommended to be multiple times the size of item displayed at once.
-     *
-     * @return The LiveData of PagedLists.
-     */
-    @AnyThread
-    @NonNull
-    public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) {
-        return create(initialLoadKey,
-                new PagedList.Config.Builder()
-                        .setPageSize(pageSize)
-                        .build());
-    }
-
-    /**
-     * Creates a LiveData of PagedLists, given the PagedList.Config.
-     * <p>
-     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
-     * {@link android.support.v7.widget.RecyclerView}.
-     *
-     * @param initialLoadKey Initial key to pass to the data source to initialize data with.
-     * @param config PagedList.Config to use with created PagedLists. This specifies how the
-     *               lists will load data.
-     *
-     * @return The LiveData of PagedLists.
-     */
-    @AnyThread
-    @NonNull
-    public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey,
-            final PagedList.Config config) {
-        return new ComputableLiveData<PagedList<Value>>() {
-            @Nullable
-            private PagedList<Value> mList;
-            @Nullable
-            private DataSource<Key, Value> mDataSource;
-
-            private final DataSource.InvalidatedCallback mCallback =
-                    new DataSource.InvalidatedCallback() {
-                @Override
-                public void onInvalidated() {
-                    invalidate();
-                }
-            };
-
-            @Override
-            protected PagedList<Value> compute() {
-                @Nullable Key initializeKey = initialLoadKey;
-                if (mList != null) {
-                    //noinspection unchecked
-                    initializeKey = (Key) mList.getLastKey();
-                }
-
-                do {
-                    if (mDataSource != null) {
-                        mDataSource.removeInvalidatedCallback(mCallback);
-                    }
-
-                    mDataSource = createDataSource();
-                    mDataSource.addInvalidatedCallback(mCallback);
-
-                    mList = new PagedList.Builder<Key, Value>()
-                            .setDataSource(mDataSource)
-                            .setMainThreadExecutor(ArchTaskExecutor.getMainThreadExecutor())
-                            .setBackgroundThreadExecutor(
-                                    ArchTaskExecutor.getIOThreadExecutor())
-                            .setConfig(config)
-                            .setInitialKey(initializeKey)
-                            .build();
-                } while (mList.isDetached());
-                return mList;
-            }
-        }.getLiveData();
-    }
-}
+abstract public class LivePagedListProvider<K, T> {
+}
\ No newline at end of file
diff --git a/android/arch/paging/NullPaddedList.java b/android/arch/paging/NullPaddedList.java
deleted file mode 100644
index c7b0b23..0000000
--- a/android/arch/paging/NullPaddedList.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.paging;
-
-import android.support.annotation.RestrictTo;
-
-import java.util.AbstractList;
-import java.util.List;
-
-/**
- * NullPaddedList is a simple list, with optional null padding on the beginning and end.
- *
- * @param <Type> The type of the entries in the list.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class NullPaddedList<Type> extends AbstractList<Type> {
-    List<Type> mList;
-    private int mTrailingNullCount;
-    private int mLeadingNullCount;
-    private int mPositionOffset;
-
-    @Override
-    public String toString() {
-        return "NullPaddedList " + mLeadingNullCount
-                + ", " + mList.size()
-                + ", " + mTrailingNullCount;
-    }
-
-    /**
-     * Create a static, immutable NullPaddedList with the specified list,
-     *
-     * @param leadingNullCount Number of empty items in advance of the passed list.
-     * @param list List of items.
-     * @param trailingNullCount Number of empty items following the passed list.
-     */
-    NullPaddedList(int leadingNullCount, List<Type> list, int trailingNullCount) {
-        if (leadingNullCount < 0 || trailingNullCount < 0) {
-            throw new IllegalArgumentException("leading/trailing null count must be non-negative");
-        }
-        if (list == null) {
-            throw new IllegalArgumentException("list must be non-null");
-        }
-        mList = list;
-        mLeadingNullCount = leadingNullCount;
-        mTrailingNullCount = trailingNullCount;
-    }
-
-    NullPaddedList(int leadingNullCount, int totalCount, List<Type> list) {
-        if (list == null) {
-            throw new IllegalArgumentException("list must be non-null");
-        }
-
-        int trailingNullCount = totalCount - (leadingNullCount) - list.size();
-
-        mList = list;
-        mLeadingNullCount = leadingNullCount;
-        mTrailingNullCount = trailingNullCount;
-    }
-
-    NullPaddedList(int positionOffset, List<Type> list) {
-        if (list == null) {
-            throw new IllegalArgumentException("list must be non-null");
-        }
-
-        mList = list;
-        mPositionOffset = positionOffset;
-    }
-
-    // --------------- PagedList API ---------------
-
-    @Override
-    public Type get(int index) {
-        if (index < 0 || index >= size()) {
-            throw new IndexOutOfBoundsException();
-        }
-
-        index -= mLeadingNullCount;
-        if (index < 0) {
-            return null;
-        }
-        if (index >= mList.size()) {
-            return null;
-        }
-        return mList.get(index);
-    }
-
-    @Override
-    public final int size() {
-        return getLoadedCount() + getLeadingNullCount() + getTrailingNullCount();
-    }
-
-    // --------------- Contiguous API ---------------
-
-    public int getPositionOffset() {
-        return mPositionOffset;
-    }
-
-    /**
-     * Number of loaded items. This does not account for leading or trailing null padding.
-     *
-     * @return Number of loaded items.
-     */
-    public int getLoadedCount() {
-        return mList.size();
-    }
-
-    /**
-     * Number of empty, unloaded items ahead of the loaded item region.
-     *
-     * @return Number of nulls before the loaded list.
-     */
-    public int getLeadingNullCount() {
-        return mLeadingNullCount;
-    }
-
-    /**
-     * Number of empty, unloaded items behind the loaded item region.
-     *
-     * @return Number of nulls after the loaded list.
-     */
-    public int getTrailingNullCount() {
-        return mTrailingNullCount;
-    }
-}
diff --git a/android/arch/paging/PageResult.java b/android/arch/paging/PageResult.java
index a4090f6..55d5fb7 100644
--- a/android/arch/paging/PageResult.java
+++ b/android/arch/paging/PageResult.java
@@ -47,6 +47,14 @@
         this.positionOffset = positionOffset;
     }
 
+    PageResult(int type) {
+        this.type = type;
+        this.page = null;
+        this.leadingNulls = 0;
+        this.trailingNulls = 0;
+        this.positionOffset = 0;
+    }
+
     interface Receiver<K, V> {
         @AnyThread
         void postOnPageResult(@NonNull PageResult<K, V> pageResult);
diff --git a/android/arch/paging/PagedList.java b/android/arch/paging/PagedList.java
index 51f524a..f18e108 100644
--- a/android/arch/paging/PagedList.java
+++ b/android/arch/paging/PagedList.java
@@ -16,8 +16,10 @@
 
 package android.arch.paging;
 
+import android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
 import android.support.annotation.WorkerThread;
 
 import java.lang.ref.WeakReference;
@@ -97,6 +99,8 @@
     final Executor mMainThreadExecutor;
     @NonNull
     final Executor mBackgroundThreadExecutor;
+    @Nullable
+    final BoundaryCallback<T> mBoundaryCallback;
     @NonNull
     final Config mConfig;
     @NonNull
@@ -105,6 +109,16 @@
     int mLastLoad = 0;
     T mLastItem = null;
 
+    // if set to true, mBoundaryCallback is non-null, and should
+    // be dispatched when nearby load has occurred
+    private boolean mBoundaryCallbackBeginDeferred = false;
+    private boolean mBoundaryCallbackEndDeferred = false;
+
+    // lowest and highest index accessed by loadAround. Used to
+    // decide when mBoundaryCallback should be dispatched
+    private int mLowestIndexAccessed = Integer.MAX_VALUE;
+    private int mHighestIndexAccessed = Integer.MIN_VALUE;
+
     private final AtomicBoolean mDetached = new AtomicBoolean(false);
 
     protected final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
@@ -112,10 +126,12 @@
     PagedList(@NonNull PagedStorage<?, T> storage,
             @NonNull Executor mainThreadExecutor,
             @NonNull Executor backgroundThreadExecutor,
+            @Nullable BoundaryCallback<T> boundaryCallback,
             @NonNull Config config) {
         mStorage = storage;
         mMainThreadExecutor = mainThreadExecutor;
         mBackgroundThreadExecutor = backgroundThreadExecutor;
+        mBoundaryCallback = boundaryCallback;
         mConfig = config;
     }
 
@@ -129,6 +145,7 @@
      *                           Generally, this is the UI/main thread.
      * @param backgroundThreadExecutor Data loading will be done via this executor - should be a
      *                                 background thread.
+     * @param boundaryCallback Optional boundary callback to attach to the list.
      * @param config PagedList Config, which defines how the PagedList will load data.
      * @param <K> Key type that indicates to the DataSource what data to load.
      * @param <T> Type of items to be held and loaded by the PagedList.
@@ -139,6 +156,7 @@
     private static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
             @NonNull Executor mainThreadExecutor,
             @NonNull Executor backgroundThreadExecutor,
+            @Nullable BoundaryCallback<T> boundaryCallback,
             @NonNull Config config,
             @Nullable K key) {
         if (dataSource.isContiguous() || !config.enablePlaceholders) {
@@ -150,12 +168,14 @@
             return new ContiguousPagedList<>(contigDataSource,
                     mainThreadExecutor,
                     backgroundThreadExecutor,
+                    boundaryCallback,
                     config,
                     key);
         } else {
             return new TiledPagedList<>((TiledDataSource<T>) dataSource,
                     mainThreadExecutor,
                     backgroundThreadExecutor,
+                    boundaryCallback,
                     config,
                     (key != null) ? (Integer) key : 0);
         }
@@ -186,6 +206,7 @@
         private DataSource<Key, Value> mDataSource;
         private Executor mMainThreadExecutor;
         private Executor mBackgroundThreadExecutor;
+        private BoundaryCallback mBoundaryCallback;
         private Config mConfig;
         private Key mInitialKey;
 
@@ -229,6 +250,14 @@
             return this;
         }
 
+        @NonNull
+        public Builder<Key, Value> setBoundaryCallback(
+                @Nullable BoundaryCallback boundaryCallback) {
+            mBoundaryCallback = boundaryCallback;
+            return this;
+        }
+
+
         /**
          * The Config defining how the PagedList should load from the DataSource.
          *
@@ -284,10 +313,12 @@
                 throw new IllegalArgumentException("Config required");
             }
 
+            //noinspection unchecked
             return PagedList.create(
                     mDataSource,
                     mMainThreadExecutor,
                     mBackgroundThreadExecutor,
+                    mBoundaryCallback,
                     mConfig,
                     mInitialKey);
         }
@@ -312,7 +343,6 @@
         return item;
     }
 
-
     /**
      * Load adjacent items to passed index.
      *
@@ -321,8 +351,122 @@
     public void loadAround(int index) {
         mLastLoad = index + getPositionOffset();
         loadAroundInternal(index);
+
+        mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
+        mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
+
+        /*
+         * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
+         * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
+         * and accesses happen near the boundaries.
+         *
+         * Note: we post here, since RecyclerView may want to add items in response, and this
+         * call occurs in PagedListAdapter bind.
+         */
+        tryDispatchBoundaryCallbacks(true);
     }
 
+    // Creation thread for initial synchronous load, otherwise main thread
+    // Safe to access main thread only state - no other thread has reference during construction
+    @AnyThread
+    void deferBoundaryCallbacks(final boolean deferEmpty,
+            final boolean deferBegin, final boolean deferEnd) {
+        if (mBoundaryCallback == null) {
+            throw new IllegalStateException("Computing boundary");
+        }
+
+        /*
+         * If lowest/highest haven't been initialized, set them to storage size,
+         * since placeholders must already be computed by this point.
+         *
+         * This is just a minor optimization so that BoundaryCallback callbacks are sent immediately
+         * if the initial load size is smaller than the prefetch window (see
+         * TiledPagedListTest#boundaryCallback_immediate())
+         */
+        if (mLowestIndexAccessed == Integer.MAX_VALUE) {
+            mLowestIndexAccessed = mStorage.size();
+        }
+        if (mHighestIndexAccessed == Integer.MIN_VALUE) {
+            mHighestIndexAccessed = 0;
+        }
+
+        if (deferEmpty || deferBegin || deferEnd) {
+            // Post to the main thread, since we may be on creation thread currently
+            mMainThreadExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    // on is dispatched immediately, since items won't be accessed
+                    //noinspection ConstantConditions
+                    if (deferEmpty) {
+                        mBoundaryCallback.onZeroItemsLoaded();
+                    }
+
+                    // for other callbacks, mark deferred, and only dispatch if loadAround
+                    // has been called near to the position
+                    if (deferBegin) {
+                        mBoundaryCallbackBeginDeferred = true;
+                    }
+                    if (deferEnd) {
+                        mBoundaryCallbackEndDeferred = true;
+                    }
+                    tryDispatchBoundaryCallbacks(false);
+                }
+            });
+        }
+    }
+
+    /**
+     * Call this when mLowest/HighestIndexAccessed are changed, or
+     * mBoundaryCallbackBegin/EndDeferred is set.
+     */
+    private void tryDispatchBoundaryCallbacks(boolean post) {
+        final boolean dispatchBegin = mBoundaryCallbackBeginDeferred
+                && mLowestIndexAccessed <= mConfig.prefetchDistance;
+        final boolean dispatchEnd = mBoundaryCallbackEndDeferred
+                && mHighestIndexAccessed >= size() - mConfig.prefetchDistance;
+
+        if (!dispatchBegin && !dispatchEnd) {
+            return;
+        }
+
+        if (dispatchBegin) {
+            mBoundaryCallbackBeginDeferred = false;
+        }
+        if (dispatchEnd) {
+            mBoundaryCallbackEndDeferred = false;
+        }
+        if (post) {
+            mMainThreadExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
+                }
+            });
+        } else {
+            dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
+        }
+    }
+
+    private void dispatchBoundaryCallbacks(boolean begin, boolean end) {
+        // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present
+        if (begin) {
+            //noinspection ConstantConditions
+            mBoundaryCallback.onItemAtFrontLoaded(
+                    snapshot(), mStorage.getFirstLoadedItem(), mStorage.size());
+        }
+        if (end) {
+            //noinspection ConstantConditions
+            mBoundaryCallback.onItemAtEndLoaded(
+                    snapshot(), mStorage.getLastLoadedItem(), mStorage.size());
+        }
+    }
+
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    void offsetBoundaryAccessIndices(int offset) {
+        mLowestIndexAccessed += offset;
+        mHighestIndexAccessed += offset;
+    }
 
     /**
      * Returns size of the list, including any not-yet-loaded null padding.
@@ -351,6 +495,7 @@
      *
      * @return Immutable snapshot of PagedList data.
      */
+    @SuppressWarnings("WeakerAccess")
     @NonNull
     public List<T> snapshot() {
         if (isImmutable()) {
@@ -726,4 +871,15 @@
             }
         }
     }
+
+    /**
+     * WIP API for load-more-into-local-storage callbacks
+     */
+    public abstract static class BoundaryCallback<T> {
+        public abstract void onZeroItemsLoaded();
+        public abstract void onItemAtFrontLoaded(@NonNull List<T> pagedListSnapshot,
+                @NonNull T itemAtFront, int pagedListSize);
+        public abstract void onItemAtEndLoaded(@NonNull List<T> pagedListSnapshot,
+                @NonNull T itemAtEnd, int pagedListSize);
+    }
 }
diff --git a/android/arch/paging/PagedListAdapter.java b/android/arch/paging/PagedListAdapter.java
index 93c02ea..89b9c2e 100644
--- a/android/arch/paging/PagedListAdapter.java
+++ b/android/arch/paging/PagedListAdapter.java
@@ -113,6 +113,13 @@
 public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder>
         extends RecyclerView.Adapter<VH> {
     private final PagedListAdapterHelper<T> mHelper;
+    private final PagedListAdapterHelper.PagedListListener<T> mListener =
+            new PagedListAdapterHelper.PagedListListener<T>() {
+        @Override
+        public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
+            PagedListAdapter.this.onCurrentListChanged(currentList);
+        }
+    };
 
     /**
      * Creates a PagedListAdapter with default threading and
@@ -125,11 +132,13 @@
      */
     protected PagedListAdapter(@NonNull DiffCallback<T> diffCallback) {
         mHelper = new PagedListAdapterHelper<>(this, diffCallback);
+        mHelper.mListener = mListener;
     }
 
     @SuppressWarnings("unused, WeakerAccess")
     protected PagedListAdapter(@NonNull ListAdapterConfig<T> config) {
         mHelper = new PagedListAdapterHelper<>(new ListAdapterHelper.AdapterCallback(this), config);
+        mHelper.mListener = mListener;
     }
 
     /**
@@ -167,4 +176,22 @@
     public PagedList<T> getCurrentList() {
         return mHelper.getCurrentList();
     }
+
+    /**
+     * Called when the current PagedList is updated.
+     * <p>
+     * This may be dispatched as part of {@link #setList(PagedList)} if a background diff isn't
+     * needed (such as when the first list is passed, or the list is cleared). In either case,
+     * PagedListAdapter will simply call
+     * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}.
+     * <p>
+     * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList
+     * to a snapshot version of the PagedList during a diff. This means you cannot observe each
+     * PagedList via this method.
+     *
+     * @param currentList new PagedList being displayed, may be null.
+     */
+    @SuppressWarnings("WeakerAccess")
+    public void onCurrentListChanged(@Nullable PagedList<T> currentList) {
+    }
 }
diff --git a/android/arch/paging/PagedListAdapterHelper.java b/android/arch/paging/PagedListAdapterHelper.java
index abcff41..51a6e37 100644
--- a/android/arch/paging/PagedListAdapterHelper.java
+++ b/android/arch/paging/PagedListAdapterHelper.java
@@ -123,6 +123,14 @@
     private final ListUpdateCallback mUpdateCallback;
     private final ListAdapterConfig<T> mConfig;
 
+    // TODO: REAL API
+    interface PagedListListener<T> {
+        void onCurrentListChanged(@Nullable PagedList<T> currentList);
+    }
+
+    @Nullable
+    PagedListListener<T> mListener;
+
     private boolean mIsContiguous;
 
     private PagedList<T> mPagedList;
@@ -247,6 +255,9 @@
             }
             // dispatch update callback after updating mPagedList/mSnapshot
             mUpdateCallback.onRemoved(0, removedCount);
+            if (mListener != null) {
+                mListener.onCurrentListChanged(null);
+            }
             return;
         }
 
@@ -257,6 +268,10 @@
 
             // dispatch update callback after updating mPagedList/mSnapshot
             mUpdateCallback.onInserted(0, pagedList.size());
+
+            if (mListener != null) {
+                mListener.onCurrentListChanged(pagedList);
+            }
             return;
         }
 
@@ -311,6 +326,9 @@
                 previousSnapshot.mStorage, newList.mStorage, diffResult);
 
         newList.addWeakCallback(diffSnapshot, mPagedListCallback);
+        if (mListener != null) {
+            mListener.onCurrentListChanged(mPagedList);
+        }
     }
 
     /**
diff --git a/android/arch/paging/PagedStorage.java b/android/arch/paging/PagedStorage.java
index 7f91290..b857462 100644
--- a/android/arch/paging/PagedStorage.java
+++ b/android/arch/paging/PagedStorage.java
@@ -230,13 +230,13 @@
 
     // ---------------- Contiguous API -------------------
 
-    V getFirstContiguousItem() {
+    V getFirstLoadedItem() {
         // safe to access first page's first item here:
         // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
         return mPages.get(0).items.get(0);
     }
 
-    V getLastContiguousItem() {
+    V getLastLoadedItem() {
         // safe to access last page's last item here:
         // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
         Page<K, V> page = mPages.get(mPages.size() - 1);
diff --git a/android/arch/paging/PositionalDataSource.java b/android/arch/paging/PositionalDataSource.java
index c538cb6..fa2932a 100644
--- a/android/arch/paging/PositionalDataSource.java
+++ b/android/arch/paging/PositionalDataSource.java
@@ -18,7 +18,6 @@
 
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
 import android.support.annotation.WorkerThread;
 
 import java.util.List;
@@ -38,10 +37,8 @@
  * backend or data store doesn't require
  * <p>
  * @param <Value> Value type of items being loaded by the DataSource.
- * @hide
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> {
+abstract class PositionalDataSource<Value> extends ContiguousDataSource<Integer, Value> {
 
     /**
      * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
@@ -66,13 +63,10 @@
         return loadBefore(currentBeginIndex - 1, pageSize);
     }
 
-    /** @hide */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @WorkerThread
-    @Nullable
     @Override
-    public NullPaddedList<Value> loadInitial(
-            Integer position, int initialLoadSize, boolean enablePlaceholders) {
+    void loadInitial(Integer position, int initialLoadSize, boolean enablePlaceholders,
+            @NonNull PageResult.Receiver<Integer, Value> receiver) {
+
         final int convertPosition = position == null ? 0 : position;
         final int loadPosition = Math.max(0, (convertPosition - initialLoadSize / 2));
 
@@ -81,11 +75,23 @@
             count = countItems();
         }
         List<Value> data = loadAfter(loadPosition, initialLoadSize);
-        if (count == COUNT_UNDEFINED) {
-            return new NullPaddedList<>(loadPosition, data);
-        } else {
-            return new NullPaddedList<>(loadPosition, count, data);
+
+        if (data == null) {
+            receiver.onPageResult(new PageResult<Integer, Value>(PageResult.INIT));
+            return;
         }
+
+        final boolean uncounted = count == COUNT_UNDEFINED;
+        int leadingNullCount = uncounted ? 0 : loadPosition;
+        int trailingNullCount = uncounted ? 0 : count - leadingNullCount - data.size();
+        int positionOffset = uncounted ? loadPosition : 0;
+
+        receiver.onPageResult(new PageResult<>(
+                PageResult.INIT,
+                new Page<Integer, Value>(data),
+                leadingNullCount,
+                trailingNullCount,
+                positionOffset));
     }
 
     /**
diff --git a/android/arch/paging/SnapshotPagedList.java b/android/arch/paging/SnapshotPagedList.java
index 7e965a0..6a8a748 100644
--- a/android/arch/paging/SnapshotPagedList.java
+++ b/android/arch/paging/SnapshotPagedList.java
@@ -27,6 +27,7 @@
         super(pagedList.mStorage.snapshot(),
                 pagedList.mMainThreadExecutor,
                 pagedList.mBackgroundThreadExecutor,
+                null,
                 pagedList.mConfig);
         mContiguous = pagedList.isContiguous();
         mLastKey = pagedList.getLastKey();
diff --git a/android/arch/paging/TiledDataSource.java b/android/arch/paging/TiledDataSource.java
index 61dead3..0ea9428 100644
--- a/android/arch/paging/TiledDataSource.java
+++ b/android/arch/paging/TiledDataSource.java
@@ -87,6 +87,8 @@
  */
 public abstract class TiledDataSource<Type> extends DataSource<Integer, Type> {
 
+    private int mItemCount;
+
     /**
      * Number of items that this DataSource can provide in total.
      *
@@ -123,6 +125,7 @@
      */
     void loadRangeInitial(int startPosition, int count, int pageSize, int itemCount,
             PageResult.Receiver<Integer, Type> receiver) {
+        mItemCount = itemCount;
 
         if (itemCount == 0) {
             // no data to load, just immediately return empty
@@ -132,7 +135,6 @@
             return;
         }
 
-
         List<Type> list = loadRangeWrapper(startPosition, count);
 
         count = Math.min(count, itemCount - startPosition);
@@ -167,9 +169,15 @@
     void loadRange(int startPosition, int count, PageResult.Receiver<Integer, Type> receiver) {
         List<Type> list = loadRangeWrapper(startPosition, count);
 
-        Page<Integer, Type> page = list != null ? new Page<Integer, Type>(list) : null;
+        Page<Integer, Type> page = null;
+        int trailingNulls = mItemCount - startPosition;
+
+        if (list != null) {
+            page = new Page<Integer, Type>(list);
+            trailingNulls -= list.size();
+        }
         receiver.postOnPageResult(new PageResult<>(
-                PageResult.TILE, page, startPosition, 0, 0));
+                PageResult.TILE, page, startPosition, trailingNulls, 0));
     }
 
     private List<Type> loadRangeWrapper(int startPosition, int count) {
diff --git a/android/arch/paging/TiledPagedList.java b/android/arch/paging/TiledPagedList.java
index 934a0dd..76bb682 100644
--- a/android/arch/paging/TiledPagedList.java
+++ b/android/arch/paging/TiledPagedList.java
@@ -17,7 +17,6 @@
 package android.arch.paging;
 
 import android.support.annotation.AnyThread;
-import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
@@ -46,7 +45,9 @@
             });
         }
 
-        @MainThread
+        // Creation thread for initial synchronous load, otherwise main thread
+        // Safe to access main thread only state - no other thread has reference during construction
+        @AnyThread
         @Override
         public void onPageResult(@NonNull PageResult<Integer, T> pageResult) {
             if (pageResult.page == null) {
@@ -67,6 +68,13 @@
                 mKeyedStorage.insertPage(pageResult.leadingNulls, pageResult.page,
                         TiledPagedList.this);
             }
+
+            if (mBoundaryCallback != null) {
+                boolean deferEmpty = mStorage.size() == 0;
+                boolean deferBegin = !deferEmpty && pageResult.leadingNulls == 0;
+                boolean deferEnd = !deferEmpty && pageResult.trailingNulls == 0;
+                deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
+            }
         }
     };
 
@@ -74,15 +82,17 @@
     TiledPagedList(@NonNull TiledDataSource<T> dataSource,
             @NonNull Executor mainThreadExecutor,
             @NonNull Executor backgroundThreadExecutor,
+            @Nullable BoundaryCallback<T> boundaryCallback,
             @NonNull Config config,
             int position) {
-        super(new PagedStorage<Integer, T>(),
-                mainThreadExecutor, backgroundThreadExecutor, config);
+        super(new PagedStorage<Integer, T>(), mainThreadExecutor, backgroundThreadExecutor,
+                boundaryCallback, config);
         mDataSource = dataSource;
 
         final int pageSize = mConfig.pageSize;
 
         final int itemCount = mDataSource.countItems();
+
         final int firstLoadSize = Math.min(itemCount,
                 (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize);
         final int firstLoadPosition = computeFirstLoadPosition(
diff --git a/android/arch/paging/integration/testapp/PagedListItemAdapter.java b/android/arch/paging/integration/testapp/PagedListItemAdapter.java
index 3522e43..d1ae5ab 100644
--- a/android/arch/paging/integration/testapp/PagedListItemAdapter.java
+++ b/android/arch/paging/integration/testapp/PagedListItemAdapter.java
@@ -23,7 +23,7 @@
 import android.widget.TextView;
 
 /**
- * Sample NullPaddedList adapter, which uses a PagedListAdapterHelper.
+ * Sample PagedList item Adapter, which uses a PagedListAdapterHelper.
  */
 class PagedListItemAdapter extends PagedListAdapter<Item, RecyclerView.ViewHolder> {
 
diff --git a/android/arch/paging/integration/testapp/PagedListItemViewModel.java b/android/arch/paging/integration/testapp/PagedListItemViewModel.java
index 237cc14..974eab9 100644
--- a/android/arch/paging/integration/testapp/PagedListItemViewModel.java
+++ b/android/arch/paging/integration/testapp/PagedListItemViewModel.java
@@ -19,7 +19,7 @@
 import android.arch.lifecycle.LiveData;
 import android.arch.lifecycle.ViewModel;
 import android.arch.paging.DataSource;
-import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.LivePagedListBuilder;
 import android.arch.paging.PagedList;
 
 /**
@@ -41,16 +41,19 @@
 
     LiveData<PagedList<Item>> getLivePagedList() {
         if (mLivePagedList == null) {
-            mLivePagedList = new LivePagedListProvider<Integer, Item>() {
-                @Override
-                protected DataSource<Integer, Item> createDataSource() {
-                    ItemDataSource newDataSource = new ItemDataSource();
-                    synchronized (mDataSourceLock) {
-                        mDataSource = newDataSource;
-                        return mDataSource;
-                    }
-                }
-            }.create(0, 20);
+            mLivePagedList = new LivePagedListBuilder<Integer, Item>()
+                    .setPagingConfig(20)
+                    .setDataSourceFactory(new DataSource.Factory<Integer, Item>() {
+                        @Override
+                        public DataSource<Integer, Item> create() {
+                            ItemDataSource newDataSource = new ItemDataSource();
+                            synchronized (mDataSourceLock) {
+                                mDataSource = newDataSource;
+                                return mDataSource;
+                            }
+                        }
+                    })
+                    .build();
         }
 
         return mLivePagedList;
diff --git a/android/arch/paging/integration/testapp/PagedListSampleActivity.java b/android/arch/paging/integration/testapp/PagedListSampleActivity.java
index 5d0117d..f1f233f 100644
--- a/android/arch/paging/integration/testapp/PagedListSampleActivity.java
+++ b/android/arch/paging/integration/testapp/PagedListSampleActivity.java
@@ -16,7 +16,6 @@
 
 package android.arch.paging.integration.testapp;
 
-import android.arch.lifecycle.LifecycleRegistry;
 import android.arch.lifecycle.Observer;
 import android.arch.lifecycle.ViewModelProviders;
 import android.arch.paging.PagedList;
@@ -28,7 +27,7 @@
 import android.widget.Button;
 
 /**
- * Sample NullPaddedList activity with artificial data source.
+ * Sample PagedList activity with artificial data source.
  */
 public class PagedListSampleActivity extends AppCompatActivity {
 
@@ -56,11 +55,4 @@
             }
         });
     }
-
-    private LifecycleRegistry  mLifecycleRegistry = new LifecycleRegistry(this);
-
-    @Override
-    public LifecycleRegistry getLifecycle() {
-        return mLifecycleRegistry;
-    }
 }
diff --git a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
index 320b2cd..89d16b7 100644
--- a/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
+++ b/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
@@ -21,7 +21,7 @@
 import android.arch.lifecycle.AndroidViewModel;
 import android.arch.lifecycle.LiveData;
 import android.arch.paging.DataSource;
-import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.LivePagedListBuilder;
 import android.arch.paging.PagedList;
 import android.arch.persistence.room.Room;
 import android.arch.persistence.room.integration.testapp.database.Customer;
@@ -81,30 +81,30 @@
         });
     }
 
+    private static <K> LiveData<PagedList<Customer>> getLivePagedList(
+            K initialLoadKey, DataSource.Factory<K, Customer> dataSourceFactory) {
+        return new LivePagedListBuilder<K, Customer>()
+                .setInitialLoadKey(initialLoadKey)
+                .setPagingConfig(new PagedList.Config.Builder()
+                        .setPageSize(10)
+                        .setEnablePlaceholders(false)
+                        .build())
+                .setDataSourceFactory(dataSourceFactory)
+                .build();
+    }
+
     LiveData<PagedList<Customer>> getLivePagedList(int position) {
         if (mLiveCustomerList == null) {
-            mLiveCustomerList = mDatabase.getCustomerDao()
-                    .loadPagedAgeOrder().create(position,
-                            new PagedList.Config.Builder()
-                                    .setPageSize(10)
-                                    .setEnablePlaceholders(false)
-                                    .build());
+            mLiveCustomerList =
+                    getLivePagedList(position, mDatabase.getCustomerDao().loadPagedAgeOrder());
         }
         return mLiveCustomerList;
     }
 
     LiveData<PagedList<Customer>> getLivePagedList(String key) {
         if (mLiveCustomerList == null) {
-            mLiveCustomerList = new LivePagedListProvider<String, Customer>() {
-                @Override
-                protected DataSource<String, Customer> createDataSource() {
-                    return new LastNameAscCustomerDataSource(mDatabase);
-                }
-            }.create(key,
-                    new PagedList.Config.Builder()
-                            .setPageSize(10)
-                            .setEnablePlaceholders(false)
-                            .build());
+            mLiveCustomerList =
+                    getLivePagedList(key, LastNameAscCustomerDataSource.factory(mDatabase));
         }
         return mLiveCustomerList;
     }
diff --git a/android/arch/persistence/room/integration/testapp/dao/UserDao.java b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
index 665a1ae..cb2bb03 100644
--- a/android/arch/persistence/room/integration/testapp/dao/UserDao.java
+++ b/android/arch/persistence/room/integration/testapp/dao/UserDao.java
@@ -17,6 +17,7 @@
 package android.arch.persistence.room.integration.testapp.dao;
 
 import android.arch.lifecycle.LiveData;
+import android.arch.paging.DataSource;
 import android.arch.paging.LivePagedListProvider;
 import android.arch.paging.TiledDataSource;
 import android.arch.persistence.room.Dao;
@@ -184,7 +185,10 @@
     }
 
     @Query("SELECT * FROM user where mAge > :age")
-    public abstract LivePagedListProvider<Integer, User> loadPagedByAge(int age);
+    public abstract DataSource.Factory<Integer, User> loadPagedByAge(int age);
+
+    @Query("SELECT * FROM user where mAge > :age")
+    public abstract LivePagedListProvider<Integer, User> loadPagedByAge_legacy(int age);
 
     @Query("SELECT * FROM user ORDER BY mAge DESC")
     public abstract TiledDataSource<User> loadUsersByAgeDesc();
@@ -200,67 +204,6 @@
     @Query("SELECT COUNT(*) from user")
     public abstract Integer getUserCount();
 
-
-    // QueryDataSourceTest - name desc
-
-    //   limit-offset
-    @Query("SELECT * from user ORDER BY mName DESC LIMIT :limit OFFSET :offset")
-    public abstract List<User> userNameLimitOffset(int limit, int offset);
-
-    //   keyed
-    @Query("SELECT * from user ORDER BY mName DESC LIMIT :limit")
-    public abstract List<User> userNameInitial(int limit);
-
-    @Query("SELECT * from user WHERE mName < :key ORDER BY mName DESC LIMIT :limit")
-    public abstract List<User> userNameLoadAfter(String key, int limit);
-
-    @Query("SELECT COUNT(*) from user WHERE mName < :key ORDER BY mName DESC")
-    public abstract int userNameCountAfter(String key);
-
-    @Query("SELECT * from user WHERE mName > :key ORDER BY mName ASC LIMIT :limit")
-    public abstract List<User> userNameLoadBefore(String key, int limit);
-
-    @Query("SELECT COUNT(*) from user WHERE mName > :key ORDER BY mName ASC")
-    public abstract int userNameCountBefore(String key);
-
-
-
-    // ComplexQueryDataSourceTest - last desc, first asc, id desc
-
-    //   limit-offset
-    @Query("SELECT * from user"
-            + " ORDER BY mLastName DESC, mName ASC, mId DESC"
-            + " LIMIT :limit OFFSET :offset")
-    public abstract List<User> userComplexLimitOffset(int limit, int offset);
-
-    //   keyed
-    @Query("SELECT * from user"
-            + " ORDER BY mLastName DESC, mName ASC, mId DESC"
-            + " LIMIT :limit")
-    public abstract List<User> userComplexInitial(int limit);
-
-    @Query("SELECT * from user"
-            + " WHERE mLastName < :lastName or (mLastName = :lastName and (mName > :name or (mName = :name and mId < :id)))"
-            + " ORDER BY mLastName DESC, mName ASC, mId DESC"
-            + " LIMIT :limit")
-    public abstract List<User> userComplexLoadAfter(String lastName, String name, int id, int limit);
-
-    @Query("SELECT COUNT(*) from user"
-            + " WHERE mLastName < :lastName or (mLastName = :lastName and (mName > :name or (mName = :name and mId < :id)))"
-            + " ORDER BY mLastName DESC, mName ASC, mId DESC")
-    public abstract int userComplexCountAfter(String lastName, String name, int id);
-
-    @Query("SELECT * from user"
-            + " WHERE mLastName > :lastName or (mLastName = :lastName and (mName < :name or (mName = :name and mId > :id)))"
-            + " ORDER BY mLastName ASC, mName DESC, mId ASC"
-            + " LIMIT :limit")
-    public abstract List<User> userComplexLoadBefore(String lastName, String name, int id, int limit);
-
-    @Query("SELECT COUNT(*) from user"
-            + " WHERE mLastName > :lastName or (mLastName = :lastName and (mName < :name or (mName = :name and mId > :id)))"
-            + " ORDER BY mLastName ASC, mName DESC, mId ASC")
-    public abstract int userComplexCountBefore(String lastName, String name, int id);
-
     @Transaction
     public void insertBothByAnnotation(final User a, final User b) {
         insert(a);
diff --git a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
index b5df914..db45dc4 100644
--- a/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
+++ b/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
@@ -16,7 +16,7 @@
 
 package android.arch.persistence.room.integration.testapp.database;
 
-import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.DataSource;
 import android.arch.persistence.room.Dao;
 import android.arch.persistence.room.Insert;
 import android.arch.persistence.room.Query;
@@ -44,12 +44,11 @@
     void insertAll(Customer[] customers);
 
     /**
-     * @return LivePagedListProvider of customers, ordered by last name. Call
-     * {@link LivePagedListProvider#create(Object, android.arch.paging.PagedList.Config)} to
-     * get a LiveData of PagedLists.
+     * @return DataSource.Factory of customers, ordered by last name. Use
+     * {@link android.arch.paging.LivePagedListBuilder} to get a LiveData of PagedLists.
      */
     @Query("SELECT * FROM customer ORDER BY mLastName ASC")
-    LivePagedListProvider<Integer, Customer> loadPagedAgeOrder();
+    DataSource.Factory<Integer, Customer> loadPagedAgeOrder();
 
     /**
      * @return number of customers
diff --git a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
index 1bc731a..a38d6ae 100644
--- a/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
+++ b/android/arch/persistence/room/integration/testapp/database/LastNameAscCustomerDataSource.java
@@ -15,6 +15,7 @@
  */
 package android.arch.persistence.room.integration.testapp.database;
 
+import android.arch.paging.DataSource;
 import android.arch.paging.KeyedDataSource;
 import android.arch.persistence.room.InvalidationTracker;
 import android.support.annotation.NonNull;
@@ -32,10 +33,19 @@
     private final InvalidationTracker.Observer mObserver;
     private SampleDatabase mDb;
 
+    public static Factory<String, Customer> factory(final SampleDatabase db) {
+        return new Factory<String, Customer>() {
+            @Override
+            public DataSource<String, Customer> create() {
+                return new LastNameAscCustomerDataSource(db);
+            }
+        };
+    }
+
     /**
      * Create a DataSource from the customer table of the given database
      */
-    public LastNameAscCustomerDataSource(SampleDatabase db) {
+    private LastNameAscCustomerDataSource(SampleDatabase db) {
         mDb = db;
         mCustomerDao = db.getCustomerDao();
         mObserver = new InvalidationTracker.Observer("customer") {
diff --git a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
similarity index 75%
rename from android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
rename to android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
index df70a17..c546531 100644
--- a/android/arch/persistence/room/integration/testapp/paging/LivePagedListProviderTest.java
+++ b/android/arch/persistence/room/integration/testapp/paging/DataSourceFactoryTest.java
@@ -28,6 +28,7 @@
 import android.arch.lifecycle.LifecycleRegistry;
 import android.arch.lifecycle.LiveData;
 import android.arch.lifecycle.Observer;
+import android.arch.paging.LivePagedListBuilder;
 import android.arch.paging.PagedList;
 import android.arch.persistence.room.integration.testapp.test.TestDatabaseTest;
 import android.arch.persistence.room.integration.testapp.test.TestUtil;
@@ -46,15 +47,54 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+@LargeTest
 @RunWith(AndroidJUnit4.class)
-public class LivePagedListProviderTest extends TestDatabaseTest {
+public class DataSourceFactoryTest extends TestDatabaseTest {
     @Rule
     public CountingTaskExecutorRule mExecutorRule = new CountingTaskExecutorRule();
 
+    private interface LivePagedListFactory {
+        LiveData<PagedList<User>> create();
+    }
+
     @Test
-    @LargeTest
     public void getUsersAsPagedList()
             throws InterruptedException, ExecutionException, TimeoutException {
+        validateUsersAsPagedList(new LivePagedListFactory() {
+            @Override
+            public LiveData<PagedList<User>> create() {
+                return new LivePagedListBuilder<Integer, User>()
+                        .setPagingConfig(new PagedList.Config.Builder()
+                                .setPageSize(10)
+                                .setPrefetchDistance(1)
+                                .setInitialLoadSizeHint(10).build())
+                        .setDataSourceFactory(mUserDao.loadPagedByAge(3))
+                        .build();
+            }
+        });
+    }
+
+
+    // TODO: delete this and factory abstraction when LivePagedListProvider is removed
+    @Test
+    public void getUsersAsPagedList_legacyLivePagedListProvider()
+            throws InterruptedException, ExecutionException, TimeoutException {
+        validateUsersAsPagedList(new LivePagedListFactory() {
+            @Override
+            public LiveData<PagedList<User>> create() {
+                return mUserDao.loadPagedByAge_legacy(3).create(
+                        0,
+                        new PagedList.Config.Builder()
+                                .setPageSize(10)
+                                .setPrefetchDistance(1)
+                                .setInitialLoadSizeHint(10)
+                                .build());
+            }
+        });
+    }
+
+    private void validateUsersAsPagedList(LivePagedListFactory factory)
+            throws InterruptedException, ExecutionException, TimeoutException {
         mDatabase.beginTransaction();
         try {
             for (int i = 0; i < 100; i++) {
@@ -67,12 +107,8 @@
             mDatabase.endTransaction();
         }
         assertThat(mUserDao.count(), is(100));
-        final LiveData<PagedList<User>> livePagedUsers = mUserDao.loadPagedByAge(3).create(
-                0,
-                new PagedList.Config.Builder()
-                        .setPageSize(10)
-                        .setPrefetchDistance(1)
-                        .setInitialLoadSizeHint(10).build());
+
+        final LiveData<PagedList<User>> livePagedUsers = factory.create();
 
         final TestLifecycleOwner testOwner = new TestLifecycleOwner();
         testOwner.handleEvent(Lifecycle.Event.ON_CREATE);
diff --git a/android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java
deleted file mode 100644
index 4d58512..0000000
--- a/android/arch/persistence/room/integration/testapp/test/ComplexQueryDataSourceTest.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import android.arch.paging.BoundedDataSource;
-import android.arch.paging.KeyedDataSource;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ComplexQueryDataSourceTest extends TestDatabaseTest {
-
-    @SuppressWarnings("WeakerAccess")
-    public class LastFirstIdKey {
-        public final String lastName;
-        public final String name;
-        public final int id;
-
-        public LastFirstIdKey(String lastName, String name, int id) {
-            this.lastName = lastName;
-            this.name = name;
-            this.id = id;
-        }
-    }
-
-    /**
-     * Proper, keyed implementation.
-     */
-    public class KeyedUserQueryDataSource extends KeyedDataSource<LastFirstIdKey, User> {
-
-        @NonNull
-        @Override
-        public LastFirstIdKey getKey(@NonNull User user) {
-            return new LastFirstIdKey(
-                    user.getLastName(),
-                    user.getName(),
-                    user.getId());
-        }
-
-        @Override
-        public int countItemsBefore(@NonNull LastFirstIdKey key) {
-            return mUserDao.userComplexCountBefore(
-                    key.lastName,
-                    key.name,
-                    key.id);
-        }
-
-        @Override
-        public int countItemsAfter(@NonNull LastFirstIdKey key) {
-            return mUserDao.userComplexCountAfter(
-                    key.lastName,
-                    key.name,
-                    key.id);
-        }
-
-        @Nullable
-        @Override
-        public List<User> loadInitial(int pageSize) {
-            return mUserDao.userComplexInitial(pageSize);
-        }
-
-        @Nullable
-        @Override
-        public List<User> loadBefore(@NonNull LastFirstIdKey key, int pageSize) {
-            return mUserDao.userComplexLoadBefore(
-                    key.lastName,
-                    key.name,
-                    key.id,
-                    pageSize);
-        }
-
-        @Nullable
-        @Override
-        public List<User> loadAfter(@Nullable LastFirstIdKey key, int pageSize) {
-            return mUserDao.userComplexLoadAfter(
-                    key.lastName,
-                    key.name,
-                    key.id,
-                    pageSize);
-        }
-    }
-
-    /**
-     * Lazy, LIMIT/OFFSET implementation.
-     */
-    public class OffsetUserQueryDataSource extends BoundedDataSource<User> {
-
-        @Override
-        public int countItems() {
-            return mUserDao.getUserCount();
-        }
-
-        @Nullable
-        @Override
-        public List<User> loadRange(int startPosition, int loadCount) {
-            return mUserDao.userComplexLimitOffset(loadCount, startPosition);
-        }
-    }
-
-    private static final User[] USERS_BY_LAST_FIRST_ID = new User[100];
-
-    @BeforeClass
-    public static void setupClass() {
-        String[] lastNames = new String[10];
-
-        String[] firstNames = new String[10];
-        for (int i = 0; i < 10; i++) {
-            lastNames[i] = "f" + (char) ('a' + i);
-            firstNames[i] = "l" + (char) ('a' + i);
-        }
-
-        for (int i = 0; i < USERS_BY_LAST_FIRST_ID.length; i++) {
-            User user = new User();
-            user.setId(i);
-            user.setName(firstNames[i % 10]);
-            user.setLastName(lastNames[(i / 10) % 10]);
-            user.setAge((int) (10 + Math.random() * 50));
-            user.setCustomField(UUID.randomUUID().toString());
-            user.setBirthday(new Date());
-            USERS_BY_LAST_FIRST_ID[i] = user;
-        }
-    }
-
-    @Before
-    public void setup() {
-        mUserDao.insertAll(USERS_BY_LAST_FIRST_ID);
-
-        Arrays.sort(USERS_BY_LAST_FIRST_ID, new Comparator<User>() {
-            @Override
-            public int compare(User o1, User o2) {
-                int diff = o2.getLastName().compareTo(o1.getLastName());
-                if (diff != 0) {
-                    return diff;
-                }
-                diff = o2.getName().compareTo(o1.getName());
-                if (diff != 0) {
-                    return -diff; // Note: 'mName' is ASC, therefore diff reversed
-                }
-
-                return o2.getId() - o1.getId();
-            }
-        });
-    }
-
-    @Test
-    public void testKeyedQueryDataSource() {
-        QueryDataSourceTest.verifyUserDataSource(USERS_BY_LAST_FIRST_ID,
-                new KeyedUserQueryDataSource());
-    }
-
-    @Test
-    public void testIndexedQueryDataSourceFull() {
-        QueryDataSourceTest.verifyUserDataSource(USERS_BY_LAST_FIRST_ID,
-                new OffsetUserQueryDataSource());
-    }
-}
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java b/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
deleted file mode 100644
index 2735c05..0000000
--- a/android/arch/persistence/room/integration/testapp/test/QueryDataSourceTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.arch.persistence.room.integration.testapp.test;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.arch.paging.BoundedDataSource;
-import android.arch.paging.ContiguousDataSource;
-import android.arch.paging.KeyedDataSource;
-import android.arch.paging.NullPaddedList;
-import android.arch.paging.PositionalDataSource;
-import android.arch.persistence.room.integration.testapp.vo.User;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.test.filters.MediumTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class QueryDataSourceTest extends TestDatabaseTest {
-    /**
-     * Proper, keyed implementation.
-     */
-    public class KeyedUserQueryDataSource extends KeyedDataSource<String, User> {
-        @NonNull
-        @Override
-        public String getKey(@NonNull User item) {
-            return item.getName();
-        }
-
-        @Override
-        public int countItemsBefore(@NonNull String userName) {
-            return mUserDao.userNameCountBefore(userName);
-        }
-
-        @Override
-        public int countItemsAfter(@NonNull String userName) {
-            return mUserDao.userNameCountAfter(userName);
-        }
-
-        @Nullable
-        @Override
-        public List<User> loadInitial(int pageSize) {
-            return mUserDao.userNameInitial(pageSize);
-        }
-
-        @Nullable
-        @Override
-        public List<User> loadBefore(@NonNull String userName, int pageSize) {
-            return mUserDao.userNameLoadBefore(userName, pageSize);
-        }
-
-        @Nullable
-        @Override
-        public List<User> loadAfter(@Nullable String userName, int pageSize) {
-            return mUserDao.userNameLoadAfter(userName, pageSize);
-        }
-    }
-
-    /**
-     * Lazy, LIMIT/OFFSET implementation.
-     */
-    public class OffsetUserQueryDataSource extends BoundedDataSource<User> {
-        @Override
-        public int countItems() {
-            return mUserDao.getUserCount();
-        }
-
-        @Nullable
-        @Override
-        public List<User> loadRange(int startPosition, int loadCount) {
-            return mUserDao.userNameLimitOffset(loadCount, startPosition);
-        }
-    }
-
-    private static final User[] USERS_BY_NAME = new User[50];
-
-    @BeforeClass
-    public static void setupClass() {
-        for (int i = 0; i < USERS_BY_NAME.length; i++) {
-            USERS_BY_NAME[i] = TestUtil.createUser(i);
-        }
-    }
-
-    @Before
-    public void setup() {
-        mUserDao.insertAll(USERS_BY_NAME);
-
-        Arrays.sort(USERS_BY_NAME, new Comparator<User>() {
-            @Override
-            public int compare(User o1, User o2) {
-                return o2.getName().compareTo(o1.getName());
-            }
-        });
-    }
-
-    @Test
-    public void testKeyedQueryDataSource() {
-        verifyUserDataSource(USERS_BY_NAME, new KeyedUserQueryDataSource());
-    }
-
-    @Test
-    public void testIndexedQueryDataSourceFull() {
-        verifyUserDataSource(USERS_BY_NAME, new OffsetUserQueryDataSource());
-    }
-
-
-    public static <Key> void verifyUserDataSource(User[] expected,
-            ContiguousDataSource<Key, User> dataSource) {
-        List<User> list = new ArrayList<>();
-
-        Object key;
-        if (dataSource instanceof PositionalDataSource) {
-            // start at 15 by loading 10 items around key 20
-            key = 20;
-        } else {
-            // start at 15 by loading 10 items around key 19 (note, keyed is exclusive, pos isn't)
-            KeyedDataSource<String, User> keyedDataSource =
-                    (KeyedDataSource<String, User>) dataSource;
-            key = keyedDataSource.getKey(expected[19]);
-        }
-        @SuppressWarnings("unchecked")
-        NullPaddedList<User> initial = dataSource.loadInitial((Key) key, 10, true);
-
-        assertNotNull(initial);
-        assertEquals(15, initial.getLeadingNullCount());
-        assertEquals(expected.length - 25, initial.getTrailingNullCount());
-        assertEquals(expected.length, initial.size());
-
-        for (int i = 15; i < initial.size() - initial.getTrailingNullCount(); i++) {
-            list.add(initial.get(i));
-        }
-
-        assertArrayEquals(Arrays.copyOfRange(expected, 15, 25), list.toArray());
-        List<User> p = dataSource.loadAfter(24, list.get(list.size() - 1), 10);
-        assertNotNull(p);
-        list.addAll(p);
-
-        assertArrayEquals(Arrays.copyOfRange(expected, 15, 35), list.toArray());
-
-        p = dataSource.loadBefore(15, list.get(0), 10);
-        assertNotNull(p);
-        list.addAll(0, p);
-
-        assertArrayEquals(Arrays.copyOfRange(expected, 5, 35), list.toArray());
-
-        p = dataSource.loadBefore(5, list.get(0), 10);
-        assertNotNull(p);
-        list.addAll(0, p);
-
-        assertArrayEquals(Arrays.copyOfRange(expected, 0, 35), list.toArray());
-    }
-}
diff --git a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
index 854c862..291cfd6 100644
--- a/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
+++ b/android/arch/persistence/room/integration/testapp/test/QueryTransactionTest.java
@@ -25,7 +25,8 @@
 import android.arch.lifecycle.Lifecycle;
 import android.arch.lifecycle.LiveData;
 import android.arch.lifecycle.Observer;
-import android.arch.paging.LivePagedListProvider;
+import android.arch.paging.DataSource;
+import android.arch.paging.LivePagedListBuilder;
 import android.arch.paging.PagedList;
 import android.arch.paging.TiledDataSource;
 import android.arch.persistence.room.Dao;
@@ -202,7 +203,10 @@
 
     @Test
     public void pagedList() {
-        LiveData<PagedList<Entity1>> pagedList = mDao.pagedList().create(null, 10);
+        LiveData<PagedList<Entity1>> pagedList = new LivePagedListBuilder<Integer, Entity1>()
+                .setDataSourceFactory(mDao.pagedList())
+                .setPagingConfig(10)
+                .build();
         observeForever(pagedList);
         drain();
         assertThat(sStartedTransactionCount.get(), is(mUseTransactionDao ? 0 : 0));
@@ -366,7 +370,7 @@
 
         List<Entity1WithChildren> withRelation();
 
-        LivePagedListProvider<Integer, Entity1> pagedList();
+        DataSource.Factory<Integer, Entity1> pagedList();
 
         TiledDataSource<Entity1> dataSource();
 
@@ -406,7 +410,7 @@
 
         @Override
         @Query(SELECT_ALL)
-        LivePagedListProvider<Integer, Entity1> pagedList();
+        DataSource.Factory<Integer, Entity1> pagedList();
 
         @Override
         @Query(SELECT_ALL)
@@ -448,7 +452,7 @@
         @Override
         @Transaction
         @Query(SELECT_ALL)
-        LivePagedListProvider<Integer, Entity1> pagedList();
+        DataSource.Factory<Integer, Entity1> pagedList();
 
         @Override
         @Transaction
diff --git a/android/bluetooth/BluetoothGattCharacteristic.java b/android/bluetooth/BluetoothGattCharacteristic.java
index 2c12403..243ad35 100644
--- a/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/android/bluetooth/BluetoothGattCharacteristic.java
@@ -120,7 +120,7 @@
     public static final int WRITE_TYPE_DEFAULT = 0x02;
 
     /**
-     * Wrtite characteristic without requiring a response by the remote device
+     * Write characteristic without requiring a response by the remote device
      */
     public static final int WRITE_TYPE_NO_RESPONSE = 0x01;
 
diff --git a/android/bluetooth/BluetoothHidDevice.java b/android/bluetooth/BluetoothHidDevice.java
index 179f36d..e3d763a 100644
--- a/android/bluetooth/BluetoothHidDevice.java
+++ b/android/bluetooth/BluetoothHidDevice.java
@@ -31,7 +31,14 @@
 import java.util.List;
 
 /**
- * @hide
+ * Provides the public APIs to control the Bluetooth HID Device
+ * profile.
+ *
+ * BluetoothHidDevice is a proxy object for controlling the Bluetooth HID
+ * Device Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHidDevice proxy object.
+ *
+ * {@hide}
  */
 public final class BluetoothHidDevice implements BluetoothProfile {
 
@@ -62,7 +69,9 @@
     /**
      * Constants representing device subclass.
      *
-     * @see #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)
+     * @see #registerApp
+     * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)
      */
     public static final byte SUBCLASS1_NONE = (byte) 0x00;
     public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
@@ -80,9 +89,9 @@
     /**
      * Constants representing report types.
      *
-     * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int)
-     * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
-     * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[])
+     * @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)
+     * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[])
      */
     public static final byte REPORT_TYPE_INPUT = (byte) 1;
     public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
@@ -91,7 +100,7 @@
     /**
      * Constants representing error response for Set Report.
      *
-     * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
+     * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
      */
     public static final byte ERROR_RSP_SUCCESS = (byte) 0;
     public static final byte ERROR_RSP_NOT_READY = (byte) 1;
@@ -104,7 +113,7 @@
      * Constants representing protocol mode used set by host. Default is always
      * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
      *
-     * @see BluetoothHidDeviceCallback#onSetProtocol(byte)
+     * @see BluetoothHidDeviceCallback#onSetProtocol(BluetoothDevice, byte)
      */
     public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
     public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
@@ -169,18 +178,7 @@
                 public void onBluetoothStateChange(boolean up) {
                     Log.d(TAG, "onBluetoothStateChange: up=" + up);
                     synchronized (mConnection) {
-                        if (!up) {
-                            Log.d(TAG, "Unbinding service...");
-                            if (mService != null) {
-                                mService = null;
-                                try {
-                                    mContext.unbindService(mConnection);
-                                } catch (IllegalArgumentException e) {
-                                    Log.e(TAG, "onBluetoothStateChange: could not unbind service:",
-                                            e);
-                                }
-                            }
-                        } else {
+                        if (up) {
                             try {
                                 if (mService == null) {
                                     Log.d(TAG, "Binding HID Device service...");
@@ -189,14 +187,15 @@
                             } catch (IllegalStateException e) {
                                 Log.e(TAG,
                                         "onBluetoothStateChange: could not bind to HID Dev "
-                                                + "service: ",
-                                        e);
+                                                + "service: ", e);
                             } catch (SecurityException e) {
                                 Log.e(TAG,
                                         "onBluetoothStateChange: could not bind to HID Dev "
-                                                + "service: ",
-                                        e);
+                                                + "service: ", e);
                             }
+                        } else {
+                            Log.d(TAG, "Unbinding service...");
+                            doUnbind();
                         }
                     }
                 }
@@ -252,6 +251,18 @@
         return true;
     }
 
+    void doUnbind() {
+        Log.d(TAG, "Unbinding HidDevService");
+        if (mService != null) {
+            mService = null;
+            try {
+                mContext.unbindService(mConnection);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Unable to unbind HidDevService", e);
+            }
+        }
+    }
+
     void close() {
         Log.v(TAG, "close()");
 
@@ -265,16 +276,8 @@
         }
 
         synchronized (mConnection) {
-            if (mService != null) {
-                mService = null;
-                try {
-                    mContext.unbindService(mConnection);
-                } catch (IllegalArgumentException e) {
-                    Log.e(TAG, "close: could not unbind HID Dev service: ", e);
-                }
-            }
+            doUnbind();
         }
-
         mServiceListener = null;
     }
 
@@ -388,7 +391,9 @@
     /**
      * Unregisters application. Active connection will be disconnected and no
      * new connections will be allowed until registered again using
-     * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
+     * {@link #registerApp
+     * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)}
      *
      * @param config {@link BluetoothHidDeviceAppConfiguration} object as obtained from {@link
      * BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
diff --git a/android/bluetooth/BluetoothHidDeviceAppConfiguration.java b/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
index 2731935..d1efa2d 100644
--- a/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
+++ b/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
@@ -21,7 +21,16 @@
 
 import java.util.Random;
 
-/** @hide */
+/**
+ * Represents the app configuration for a Bluetooth HID Device application.
+ *
+ * The app needs a BluetoothHidDeviceAppConfiguration token to unregister
+ * the Bluetooth HID Device service.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
 public final class BluetoothHidDeviceAppConfiguration implements Parcelable {
     private final long mHash;
 
diff --git a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
index 1f80ed7..ccc3ef4 100644
--- a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
+++ b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -19,7 +19,17 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-/** @hide */
+/**
+ * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device
+ * application.
+ *
+ * The BluetoothHidDevice framework will update the L2CAP QoS settings for the
+ * app during registration.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
 public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
 
     public final int serviceType;
@@ -36,8 +46,7 @@
     public static final int MAX = (int) 0xffffffff;
 
     public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize,
-            int peakBandwidth,
-            int latency, int delayVariation) {
+            int peakBandwidth, int latency, int delayVariation) {
         this.serviceType = serviceType;
         this.tokenRate = tokenRate;
         this.tokenBucketSize = tokenBucketSize;
@@ -66,10 +75,13 @@
                 @Override
                 public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) {
 
-                    return new BluetoothHidDeviceAppQosSettings(in.readInt(), in.readInt(),
+                    return new BluetoothHidDeviceAppQosSettings(
                             in.readInt(),
                             in.readInt(),
-                            in.readInt(), in.readInt());
+                            in.readInt(),
+                            in.readInt(),
+                            in.readInt(),
+                            in.readInt());
                 }
 
                 @Override
@@ -90,7 +102,7 @@
 
     /** @return an int array representation of this instance */
     public int[] toArray() {
-        return new int[]{
+        return new int[] {
                 serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
         };
     }
diff --git a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
index d21d506..f01c493 100644
--- a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
+++ b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -19,7 +19,18 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-/** @hide */
+/**
+ * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth
+ * HID Device application.
+ *
+ * The BluetoothHidDevice framework adds the SDP record during app
+ * registration, so that the Android device can be discovered as a Bluetooth
+ * HID Device.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
 public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
 
     public final String name;
@@ -57,8 +68,12 @@
                 @Override
                 public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) {
 
-                    return new BluetoothHidDeviceAppSdpSettings(in.readString(), in.readString(),
-                            in.readString(), in.readByte(), in.createByteArray());
+                    return new BluetoothHidDeviceAppSdpSettings(
+                            in.readString(),
+                            in.readString(),
+                            in.readString(),
+                            in.readByte(),
+                            in.createByteArray());
                 }
 
                 @Override
diff --git a/android/bluetooth/BluetoothHidDeviceCallback.java b/android/bluetooth/BluetoothHidDeviceCallback.java
index 3d407a6..5ccda0d 100644
--- a/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -18,16 +18,24 @@
 
 import android.util.Log;
 
-/** @hide */
+/**
+ * The template class that applications use to call callback functions on
+ * events from the HID host. Callback functions are wrapped in this class and
+ * registered to the Android system during app registration.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
 public abstract class BluetoothHidDeviceCallback {
 
-    private static final String TAG = BluetoothHidDeviceCallback.class.getSimpleName();
+    private static final String TAG = "BluetoothHidDevCallback";
 
     /**
      * Callback called when application registration state changes. Usually it's
      * called due to either
-     * {@link BluetoothHidDevice#registerApp(String, String, String, byte, byte[],
-     * BluetoothHidDeviceCallback)}
+     * {@link BluetoothHidDevice#registerApp
+     * (String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
      * or
      * {@link BluetoothHidDevice#unregisterApp(BluetoothHidDeviceAppConfiguration)}
      * , but can be also unsolicited in case e.g. Bluetooth was turned off in
@@ -79,7 +87,7 @@
     /**
      * Callback called when SET_REPORT is received from remote host. In case
      * received data are invalid, application shall respond with
-     * {@link BluetoothHidDevice#reportError(BluetoothDevice)}.
+     * {@link BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
      *
      * @param type Report Type.
      * @param id Report Id.
diff --git a/android/content/pm/ApplicationInfo.java b/android/content/pm/ApplicationInfo.java
index d73f852..2034280 100644
--- a/android/content/pm/ApplicationInfo.java
+++ b/android/content/pm/ApplicationInfo.java
@@ -375,7 +375,7 @@
      * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext
      * traffic. Third-party libraries are encouraged to honor this flag as well.
      *
-     * <p>NOTE: {@code WebView} does not honor this flag.
+     * <p>NOTE: {@code WebView} honors this flag for applications targeting API level 26 and up.
      *
      * <p>This flag is ignored on Android N and above if an Android Network Security Config is
      * present.
@@ -1463,47 +1463,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
-    public boolean isForwardLocked() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
-    }
-
-    /**
-     * @hide
-     */
-    @TestApi
-    public boolean isSystemApp() {
-        return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-    }
-
-    /**
-     * @hide
-     */
-    @TestApi
-    public boolean isPrivilegedApp() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
-    }
-
-    /**
-     * @hide
-     */
-    public boolean isUpdatedSystemApp() {
-        return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
-    }
-
-    /** @hide */
-    public boolean isInternal() {
-        return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
-    }
-
-    /** @hide */
-    public boolean isExternalAsec() {
-        return TextUtils.isEmpty(volumeUuid)
-                && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
-    }
-
     /** @hide */
     public boolean isDefaultToDeviceProtectedStorage() {
         return (privateFlags
@@ -1516,45 +1475,72 @@
     }
 
     /** @hide */
+    public boolean isEncryptionAware() {
+        return isDirectBootAware() || isPartiallyDirectBootAware();
+    }
+
+    /** @hide */
+    public boolean isExternal() {
+        return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+    }
+
+    /** @hide */
+    public boolean isExternalAsec() {
+        return TextUtils.isEmpty(volumeUuid) && isExternal();
+    }
+
+    /** @hide */
+    public boolean isForwardLocked() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
+    }
+
+    /** @hide */
+    public boolean isInstantApp() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+    }
+
+    /** @hide */
+    public boolean isInternal() {
+        return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
+    }
+
+    /** @hide */
+    public boolean isOem() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+    }
+
+    /** @hide */
     public boolean isPartiallyDirectBootAware() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0;
     }
 
     /** @hide */
-    public boolean isEncryptionAware() {
-        return isDirectBootAware() || isPartiallyDirectBootAware();
+    @TestApi
+    public boolean isPrivilegedApp() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
 
-    /**
-     * @hide
-     */
-    public boolean isInstantApp() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
-    }
-
-    /**
-     * @hide
-     */
+    /** @hide */
     public boolean isRequiredForSystemUser() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
     }
 
-    /**
-     * Returns true if the app has declared in its manifest that it wants its split APKs to be
-     * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
-     * @hide
-     */
-    public boolean requestsIsolatedSplitLoading() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
-    }
-
-    /**
-     * @hide
-     */
+    /** @hide */
     public boolean isStaticSharedLibrary() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0;
     }
 
+    /** @hide */
+    @TestApi
+    public boolean isSystemApp() {
+        return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    /** @hide */
+    public boolean isUpdatedSystemApp() {
+        return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
     /**
      * Returns whether or not this application was installed as a virtual preload.
      */
@@ -1563,10 +1549,12 @@
     }
 
     /**
+     * Returns true if the app has declared in its manifest that it wants its split APKs to be
+     * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
      * @hide
      */
-    public boolean isOem() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+    public boolean requestsIsolatedSplitLoading() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
     }
 
     /**
diff --git a/android/content/pm/PackageManagerInternal.java b/android/content/pm/PackageManagerInternal.java
index 143c51d..14cf855 100644
--- a/android/content/pm/PackageManagerInternal.java
+++ b/android/content/pm/PackageManagerInternal.java
@@ -337,6 +337,12 @@
     public abstract boolean isPackagePersistent(String packageName);
 
     /**
+     * Returns whether or not the given package represents a legacy system application released
+     * prior to runtime permissions.
+     */
+    public abstract boolean isLegacySystemApp(PackageParser.Package pkg);
+
+    /**
      * Get all overlay packages for a user.
      * @param userId The user for which to get the overlays.
      * @return A list of overlay packages. An empty list is returned if the
@@ -467,7 +473,4 @@
     /** Updates the flags for the given permission. */
     public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
             @NonNull String packageName, int flagMask, int flagValues, int userId);
-    /** Returns a PermissionGroup. */
-    public abstract @Nullable PackageParser.PermissionGroup getPermissionGroupTEMP(
-            @NonNull String groupName);
 }
diff --git a/android/content/pm/PackageParser.java b/android/content/pm/PackageParser.java
index ad36139..b48829c 100644
--- a/android/content/pm/PackageParser.java
+++ b/android/content/pm/PackageParser.java
@@ -6221,48 +6221,48 @@
             return false;
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
+        public boolean isExternal() {
+            return applicationInfo.isExternal();
+        }
+
+        /** @hide */
         public boolean isForwardLocked() {
             return applicationInfo.isForwardLocked();
         }
 
-        /**
-         * @hide
-         */
-        public boolean isSystemApp() {
-            return applicationInfo.isSystemApp();
+        /** @hide */
+        public boolean isOem() {
+            return applicationInfo.isOem();
         }
 
-        /**
-         * @hide
-         */
-        public boolean isPrivilegedApp() {
+        /** @hide */
+        public boolean isPrivileged() {
             return applicationInfo.isPrivilegedApp();
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
+        public boolean isSystem() {
+            return applicationInfo.isSystemApp();
+        }
+
+        /** @hide */
         public boolean isUpdatedSystemApp() {
             return applicationInfo.isUpdatedSystemApp();
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
         public boolean canHaveOatDir() {
             // The following app types CANNOT have oat directory
             // - non-updated system apps
             // - forward-locked apps or apps installed in ASEC containers
-            return (!isSystemApp() || isUpdatedSystemApp())
+            return (!isSystem() || isUpdatedSystemApp())
                     && !isForwardLocked() && !applicationInfo.isExternalAsec();
         }
 
         public boolean isMatch(int flags) {
             if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
-                return isSystemApp();
+                return isSystem();
             }
             return true;
         }
diff --git a/android/content/res/Resources_Delegate.java b/android/content/res/Resources_Delegate.java
index c1e9cd3..d9c97fe 100644
--- a/android/content/res/Resources_Delegate.java
+++ b/android/content/res/Resources_Delegate.java
@@ -651,6 +651,26 @@
     }
 
     @LayoutlibDelegate
+    static float getFloat(Resources resources, int id) {
+        Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
+
+        if (value != null) {
+            ResourceValue resValue = value.getSecond();
+
+            if (resValue != null) {
+                String v = resValue.getValue();
+                if (v != null) {
+                    try {
+                        return Float.parseFloat(v);
+                    } catch (NumberFormatException ignore) {
+                    }
+                }
+            }
+        }
+        return 0;
+    }
+
+    @LayoutlibDelegate
     static boolean getBoolean(Resources resources, int id) throws NotFoundException {
         Pair<String, ResourceValue> value = getResourceValue(resources, id, mPlatformResourceFlag);
 
diff --git a/android/hardware/location/ContextHubClient.java b/android/hardware/location/ContextHubClient.java
new file mode 100644
index 0000000..b7e353a
--- /dev/null
+++ b/android/hardware/location/ContextHubClient.java
@@ -0,0 +1,95 @@
+/*
+ * 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.location;
+
+import android.annotation.RequiresPermission;
+import android.os.Handler;
+
+import java.io.Closeable;
+
+/**
+ * A class describing a client of the Context Hub Service.
+ *
+ * Clients can send messages to nanoapps at a Context Hub through this object.
+ *
+ * @hide
+ */
+public class ContextHubClient implements Closeable {
+    /*
+     * The ContextHubClient interface associated with this client.
+     */
+    // TODO: Implement this interface and associate with ContextHubClient object
+    // private final IContextHubClient mClientInterface;
+
+    /*
+     * The listening callback associated with this client.
+     */
+    private ContextHubClientCallback mCallback;
+
+    /*
+     * The Context Hub that this client is attached to.
+     */
+    private ContextHubInfo mAttachedHub;
+
+    /*
+     * The handler to invoke mCallback.
+     */
+    private Handler mCallbackHandler;
+
+    ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) {
+        mCallback = callback;
+        mCallbackHandler = handler;
+        mAttachedHub = hubInfo;
+    }
+
+    /**
+     * Returns the hub that this client is attached to.
+     *
+     * @return the ContextHubInfo of the attached hub
+     */
+    public ContextHubInfo getAttachedHub() {
+        return mAttachedHub;
+    }
+
+    /**
+     * Closes the connection for this client and the Context Hub Service.
+     *
+     * When this function is invoked, the messaging associated with this client is invalidated.
+     * All futures messages targeted for this client are dropped at the service.
+     */
+    public void close() {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Sends a message to a nanoapp through the Context Hub Service.
+     *
+     * This function returns TRANSACTION_SUCCESS if the message has reached the HAL, but
+     * does not guarantee delivery of the message to the target nanoapp.
+     *
+     * @param message the message object to send
+     *
+     * @return the result of sending the message defined as in ContextHubTransaction.Result
+     *
+     * @see NanoAppMessage
+     * @see ContextHubTransaction.Result
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    @ContextHubTransaction.Result
+    public int sendMessageToNanoApp(NanoAppMessage message) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+}
diff --git a/android/hardware/location/ContextHubClientCallback.java b/android/hardware/location/ContextHubClientCallback.java
new file mode 100644
index 0000000..ab19d54
--- /dev/null
+++ b/android/hardware/location/ContextHubClientCallback.java
@@ -0,0 +1,85 @@
+/*
+ * 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.location;
+
+/**
+ * A class for {@link android.hardware.location.ContextHubClient ContextHubClient} to
+ * receive messages and life-cycle events from nanoapps in the Context Hub at which the client is
+ * attached to.
+ *
+ * This callback is registered through the
+ * {@link android.hardware.location.ContextHubManager#createClient() creation} of
+ * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are
+ * invoked in the following ways:
+ * 1) Messages from nanoapps delivered through onMessageFromNanoApp may either be broadcasted
+ *    or targeted to a specific client.
+ * 2) Nanoapp or Context Hub events (the remaining callbacks) are broadcasted to all clients, and
+ *    the client can choose to ignore the event by filtering through the parameters.
+ *
+ * @hide
+ */
+public class ContextHubClientCallback {
+    /**
+     * Callback invoked when receiving a message from a nanoapp.
+     *
+     * The message contents of this callback may either be broadcasted or targeted to the
+     * client receiving the invocation.
+     *
+     * @param message the message sent by the nanoapp
+     */
+    public void onMessageFromNanoApp(NanoAppMessage message) {}
+
+    /**
+     * Callback invoked when the attached Context Hub has reset.
+     */
+    public void onHubReset() {}
+
+    /**
+     * Callback invoked when a nanoapp aborts at the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had aborted
+     * @param abortCode the reason for nanoapp's abort, specific to each nanoapp
+     */
+    public void onNanoAppAborted(long nanoAppId, int abortCode) {}
+
+    /**
+     * Callback invoked when a nanoapp is loaded at the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had been loaded
+     */
+    public void onNanoAppLoaded(long nanoAppId) {}
+
+    /**
+     * Callback invoked when a nanoapp is unloaded from the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had been unloaded
+     */
+    public void onNanoAppUnloaded(long nanoAppId) {}
+
+    /**
+     * Callback invoked when a nanoapp is enabled at the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had been enabled
+     */
+    public void onNanoAppEnabled(long nanoAppId) {}
+
+    /**
+     * Callback invoked when a nanoapp is disabled at the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had been disabled
+     */
+    public void onNanoAppDisabled(long nanoAppId) {}
+}
diff --git a/android/hardware/location/ContextHubManager.java b/android/hardware/location/ContextHubManager.java
index 6050046..7cbb436 100644
--- a/android/hardware/location/ContextHubManager.java
+++ b/android/hardware/location/ContextHubManager.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -27,6 +28,8 @@
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.util.Log;
 
+import java.util.List;
+
 /**
  * A class that exposes the Context hubs on a device to applications.
  *
@@ -38,7 +41,6 @@
 @SystemApi
 @SystemService(Context.CONTEXTHUB_SERVICE)
 public final class ContextHubManager {
-
     private static final String TAG = "ContextHubManager";
 
     private final Looper mMainLooper;
@@ -256,6 +258,100 @@
     }
 
     /**
+     * Returns the list of context hubs in the system.
+     *
+     * @return the list of context hub informations
+     *
+     * @see ContextHubInfo
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public List<ContextHubInfo> getContextHubs() {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Loads a nanoapp at the specified Context Hub.
+     *
+     * After the nanoapp binary is successfully loaded at the specified hub, the nanoapp will be in
+     * the enabled state.
+     *
+     * @param hubInfo the hub to load the nanoapp on
+     * @param appBinary The app binary to load
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @see NanoAppBinary
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<Void> loadNanoApp(
+            ContextHubInfo hubInfo, NanoAppBinary appBinary) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Unloads a nanoapp at the specified Context Hub.
+     *
+     * @param hubInfo the hub to unload the nanoapp from
+     * @param nanoAppId the app to unload
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<Void> unloadNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Enables a nanoapp at the specified Context Hub.
+     *
+     * @param hubInfo the hub to enable the nanoapp on
+     * @param nanoAppId the app to enable
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<Void> enableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Disables a nanoapp at the specified Context Hub.
+     *
+     * @param hubInfo the hub to disable the nanoapp on
+     * @param nanoAppId the app to disable
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<Void> disableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Requests a query for nanoapps loaded at the specified Context Hub.
+     *
+     * @param hubInfo the hub to query a list of nanoapps from
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<List<NanoAppState>> queryNanoApps(ContextHubInfo hubInfo) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
      * Set a callback to receive messages from the context hub
      *
      * @param callback Callback object
@@ -307,6 +403,29 @@
     }
 
     /**
+     * Creates and registers a client and its callback with the Context Hub Service.
+     *
+     * A client is registered with the Context Hub Service for a specified Context Hub. When the
+     * registration succeeds, the client can send messages to nanoapps through the returned
+     * {@link ContextHubClient} object, and receive notifications through the provided callback.
+     *
+     * @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
+     *
+     * @return the registered client object
+     *
+     * @see ContextHubClientCallback
+     *
+     * @hide
+     */
+    public ContextHubClient createClient(
+            ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
+        throw new UnsupportedOperationException(
+                "TODO: Implement this, and throw an exception on error");
+    }
+
+    /**
      * Unregister a callback for receive messages from the context hub.
      *
      * @see Callback
diff --git a/android/hardware/location/ContextHubTransaction.java b/android/hardware/location/ContextHubTransaction.java
new file mode 100644
index 0000000..4877d38
--- /dev/null
+++ b/android/hardware/location/ContextHubTransaction.java
@@ -0,0 +1,229 @@
+/*
+ * 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.location;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class describing a request sent to the Context Hub Service.
+ *
+ * This object is generated as a result of an asynchronous request sent to the Context Hub
+ * 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)}.
+ *
+ * @param <T> the type of the contents in the transaction response
+ *
+ * @hide
+ */
+public class ContextHubTransaction<T> {
+    /**
+     * Constants describing the type of a transaction through the Context Hub Service.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            TYPE_LOAD_NANOAPP,
+            TYPE_UNLOAD_NANOAPP,
+            TYPE_ENABLE_NANOAPP,
+            TYPE_DISABLE_NANOAPP,
+            TYPE_QUERY_NANOAPPS})
+    public @interface Type {}
+    public static final int TYPE_LOAD_NANOAPP = 0;
+    public static final int TYPE_UNLOAD_NANOAPP = 1;
+    public static final int TYPE_ENABLE_NANOAPP = 2;
+    public static final int TYPE_DISABLE_NANOAPP = 3;
+    public static final int TYPE_QUERY_NANOAPPS = 4;
+
+    /**
+     * Constants describing the result of a transaction or request through the Context Hub Service.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            TRANSACTION_SUCCESS,
+            TRANSACTION_FAILED_UNKNOWN,
+            TRANSACTION_FAILED_BAD_PARAMS,
+            TRANSACTION_FAILED_UNINITIALIZED,
+            TRANSACTION_FAILED_PENDING,
+            TRANSACTION_FAILED_AT_HUB,
+            TRANSACTION_FAILED_TIMEOUT})
+    public @interface Result {}
+    public static final int TRANSACTION_SUCCESS = 0;
+    /**
+     * Generic failure mode.
+     */
+    public static final int TRANSACTION_FAILED_UNKNOWN = 1;
+    /**
+     * Failure mode when the request parameters were not valid.
+     */
+    public static final int TRANSACTION_FAILED_BAD_PARAMS = 2;
+    /**
+     * Failure mode when the Context Hub is not initialized.
+     */
+    public static final int TRANSACTION_FAILED_UNINITIALIZED = 3;
+    /**
+     * Failure mode when there are too many transactions pending.
+     */
+    public static final int TRANSACTION_FAILED_PENDING = 4;
+    /**
+     * Failure mode when the request went through, but failed asynchronously at the hub.
+     */
+    public static final int TRANSACTION_FAILED_AT_HUB = 5;
+    /**
+     * Failure mode when the transaction has timed out.
+     */
+    public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+
+    /**
+     * A class describing the response for a ContextHubTransaction.
+     *
+     * @param <R> the type of the contents in the response
+     */
+    public static class Response<R> {
+        /*
+         * The result of the transaction.
+         */
+        @ContextHubTransaction.Result
+        private int mResult;
+
+        /*
+         * The contents of the response from the Context Hub.
+         */
+        private R mContents;
+
+        Response(@ContextHubTransaction.Result int result, R contents) {
+            mResult = result;
+            mContents = contents;
+        }
+
+        @ContextHubTransaction.Result
+        public int getResult() {
+            return mResult;
+        }
+
+        public R getContents() {
+            return mContents;
+        }
+    }
+
+    /**
+     * An interface describing the callback to be invoked when a transaction completes.
+     *
+     * @param <C> the type of the contents in the transaction response
+     */
+    @FunctionalInterface
+    public interface Callback<C> {
+        /**
+         * The callback to invoke when the transaction completes.
+         *
+         * @param transaction the transaction that this callback was attached to.
+         * @param response the response of the transaction.
+         */
+        void onComplete(
+                ContextHubTransaction<C> transaction, ContextHubTransaction.Response<C> response);
+    }
+
+    /*
+     * The unique identifier representing the transaction.
+     */
+    private int mTransactionId;
+
+    /*
+     * The type of the transaction.
+     */
+    @Type
+    private int mTransactionType;
+
+    /*
+     * The response of the transaction.
+     */
+    private ContextHubTransaction.Response<T> mResponse;
+
+    /*
+     * The handler to invoke the aynsc response supplied by onComplete.
+     */
+    private Handler mHandler = null;
+
+    /*
+     * The callback to invoke when the transaction completes.
+     */
+    private ContextHubTransaction.Callback<T> mCallback = null;
+
+    ContextHubTransaction(int id, @Type int type) {
+        mTransactionId = id;
+        mTransactionType = type;
+    }
+
+    /**
+     * @return the type of the transaction
+     */
+    @Type
+    public int getType() {
+        return mTransactionType;
+    }
+
+    /**
+     * Waits to receive the asynchronous transaction result.
+     *
+     * This function blocks until the Context Hub Service has received a response
+     * 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).
+     *
+     * @param timeout the timeout duration
+     * @param unit the unit of the timeout
+     *
+     * @return the transaction response
+     */
+    public ContextHubTransaction.Response<T> waitForResponse(long timeout, TimeUnit unit) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Sets a callback to be invoked when the transaction completes.
+     *
+     * This function provides an asynchronous approach to retrieve the result of the
+     * transaction. When the transaction response has been provided by the Context Hub,
+     * the given callback will be posted by the provided handler.
+     *
+     * If the transaction has already completed at the time of invocation, the callback
+     * will be immediately posted by the handler. If the transaction has been invalidated,
+     * the callback will never be invoked.
+     *
+     * @param callback the callback to be invoked upon completion
+     * @param handler the handler to post the callback
+     */
+    public void onComplete(ContextHubTransaction.Callback<T> callback, Handler handler) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    private void setResponse(ContextHubTransaction.Response<T> response) {
+        mResponse = response;
+        throw new UnsupportedOperationException("TODO: Unblock waitForResponse");
+    }
+}
diff --git a/android/hardware/location/NanoAppBinary.java b/android/hardware/location/NanoAppBinary.java
new file mode 100644
index 0000000..5454227
--- /dev/null
+++ b/android/hardware/location/NanoAppBinary.java
@@ -0,0 +1,198 @@
+/*
+ * 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.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * @hide
+ */
+public final class NanoAppBinary implements Parcelable {
+    private static final String TAG = "NanoAppBinary";
+
+    /*
+     * The contents of the app binary.
+     */
+    private byte[] mNanoAppBinary;
+
+    /*
+     * Contents of the nanoapp binary header.
+     *
+     * Only valid if mHasValidHeader is true.
+     * See nano_app_binary_t in context_hub.h for details.
+     */
+    private int mHeaderVersion;
+    private int mMagic;
+    private long mNanoAppId;
+    private int mNanoAppVersion;
+    private int mFlags;
+    private long mHwHubType;
+    private byte mTargetChreApiMajorVersion;
+    private byte mTargetChreApiMinorVersion;
+
+    private boolean mHasValidHeader = false;
+
+    /*
+     * The header version used to parse the binary in parseBinaryHeader().
+     */
+    private static final int EXPECTED_HEADER_VERSION = 1;
+
+    /*
+     * The magic value expected in the header.
+     */
+    private static final int EXPECTED_MAGIC_VALUE =
+            (((int) 'N' <<  0) | ((int) 'A' <<  8) | ((int) 'N' << 16) | ((int) 'O' << 24));
+
+    /*
+     * Byte order established in context_hub.h
+     */
+    private static final ByteOrder HEADER_ORDER = ByteOrder.LITTLE_ENDIAN;
+
+    public NanoAppBinary(byte[] appBinary) {
+        mNanoAppBinary = appBinary;
+        parseBinaryHeader();
+    }
+
+    /*
+     * Parses the binary header and populates its field using mNanoAppBinary.
+     */
+    private void parseBinaryHeader() {
+        ByteBuffer buf = ByteBuffer.wrap(mNanoAppBinary).order(HEADER_ORDER);
+
+        mHasValidHeader = false;
+        try {
+            mHeaderVersion = buf.getInt();
+            if (mHeaderVersion != EXPECTED_HEADER_VERSION) {
+                Log.e(TAG, "Unexpected header version " + mHeaderVersion + " while parsing header"
+                        + " (expected " + EXPECTED_HEADER_VERSION + ")");
+                return;
+            }
+
+            mMagic = buf.getInt();
+            mNanoAppId = buf.getLong();
+            mNanoAppVersion = buf.getInt();
+            mFlags = buf.getInt();
+            mHwHubType = buf.getLong();
+            mTargetChreApiMajorVersion = buf.get();
+            mTargetChreApiMinorVersion = buf.get();
+        } catch (BufferUnderflowException e) {
+            Log.e(TAG, "Not enough contents in nanoapp header");
+            return;
+        }
+
+        if (mMagic != EXPECTED_MAGIC_VALUE) {
+            Log.e(TAG, "Unexpected magic value " + String.format("0x%08X", mMagic)
+                    + "while parsing header (expected "
+                    + String.format("0x%08X", EXPECTED_MAGIC_VALUE) + ")");
+        } else {
+            mHasValidHeader = true;
+        }
+    }
+
+    /**
+     * @return the app binary byte array
+     */
+    public byte[] getNanoAppBinary() {
+        return mNanoAppBinary;
+    }
+
+    /**
+     * @return {@code true} if the header is valid, {@code false} otherwise
+     */
+    public boolean hasValidHeader() {
+        return mHasValidHeader;
+    }
+
+    /**
+     * @return the header version
+     */
+    public int getHeaderVersion() {
+        return mHeaderVersion;
+    }
+
+    /**
+     * @return the app ID parsed from the nanoapp header
+     */
+    public long getNanoAppId() {
+        return mNanoAppId;
+    }
+
+    /**
+     * @return the app version parsed from the nanoapp header
+     */
+    public int getNanoAppVersion() {
+        return mNanoAppVersion;
+    }
+
+    /**
+     * @return the compile target hub type parsed from the nanoapp header
+     */
+    public long getHwHubType() {
+        return mHwHubType;
+    }
+
+    /**
+     * @return the target CHRE API major version parsed from the nanoapp header
+     */
+    public byte getTargetChreApiMajorVersion() {
+        return mTargetChreApiMajorVersion;
+    }
+
+    /**
+     * @return the target CHRE API minor version parsed from the nanoapp header
+     */
+    public byte getTargetChreApiMinorVersion() {
+        return mTargetChreApiMinorVersion;
+    }
+
+    private NanoAppBinary(Parcel in) {
+        int binaryLength = in.readInt();
+        mNanoAppBinary = new byte[binaryLength];
+        in.readByteArray(mNanoAppBinary);
+
+        parseBinaryHeader();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mNanoAppBinary.length);
+        out.writeByteArray(mNanoAppBinary);
+    }
+
+    public static final Creator<NanoAppBinary> CREATOR =
+            new Creator<NanoAppBinary>() {
+                @Override
+                public NanoAppBinary createFromParcel(Parcel in) {
+                    return new NanoAppBinary(in);
+                }
+
+                @Override
+                public NanoAppBinary[] newArray(int size) {
+                    return new NanoAppBinary[size];
+                }
+            };
+}
diff --git a/android/hardware/location/NanoAppMessage.java b/android/hardware/location/NanoAppMessage.java
new file mode 100644
index 0000000..2028674
--- /dev/null
+++ b/android/hardware/location/NanoAppMessage.java
@@ -0,0 +1,143 @@
+/*
+ * 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.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class describing messages send to or from nanoapps through the Context Hub Service.
+ *
+ * The basis of the class is in the IContextHub.hal ContextHubMsg definition.
+ *
+ * @hide
+ */
+public final class NanoAppMessage implements Parcelable {
+    private long mNanoAppId;
+    private int mMessageType;
+    private byte[] mMessageBody;
+    private boolean mIsBroadcasted;
+
+    private NanoAppMessage(
+            long nanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+        mNanoAppId = nanoAppId;
+        mMessageType = messageType;
+        mMessageBody = messageBody;
+        mIsBroadcasted = broadcasted;
+    }
+
+    /**
+     * Creates a NanoAppMessage object to send to a nanoapp.
+     *
+     * This factory method can be used to generate a NanoAppMessage object to be used in
+     * the ContextHubClient.sendMessageToNanoApp API.
+     *
+     * @param targetNanoAppId the ID of the nanoapp to send the message to
+     * @param messageType the nanoapp-dependent message type
+     * @param messageBody the byte array message contents
+     *
+     * @return the NanoAppMessage object
+     */
+    public static NanoAppMessage createMessageToNanoApp(
+            long targetNanoAppId, int messageType, byte[] messageBody) {
+        return new NanoAppMessage(
+                targetNanoAppId, messageType, messageBody, false /* broadcasted */);
+    }
+
+    /**
+     * Creates a NanoAppMessage object sent from a nanoapp.
+     *
+     * This factory method is intended only to be used by the Context Hub Service when delivering
+     * messages from a nanoapp to clients.
+     *
+     * @param sourceNanoAppId the ID of the nanoapp that the message was sent from
+     * @param messageType the nanoapp-dependent message type
+     * @param messageBody the byte array message contents
+     * @param broadcasted {@code true} if the message was broadcasted, {@code false} otherwise
+     *
+     * @return the NanoAppMessage object
+     */
+    public static NanoAppMessage createMessageFromNanoApp(
+            long sourceNanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+        return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted);
+    }
+
+    /**
+     * @return the ID of the source or destination nanoapp
+     */
+    public long getNanoAppId() {
+        return mNanoAppId;
+    }
+
+    /**
+     * @return the type of the message that is nanoapp-dependent
+     */
+    public int getMessageType() {
+        return mMessageType;
+    }
+
+    /**
+     * @return the byte array contents of the message
+     */
+    public byte[] getMessageBody() {
+        return mMessageBody;
+    }
+
+    /**
+     * @return {@code true} if the message is broadcasted, {@code false} otherwise
+     */
+    public boolean isBroadcastMessage() {
+        return mIsBroadcasted;
+    }
+
+    private NanoAppMessage(Parcel in) {
+        mNanoAppId = in.readLong();
+        mIsBroadcasted = (in.readInt() == 1);
+        mMessageType = in.readInt();
+
+        int msgSize = in.readInt();
+        mMessageBody = new byte[msgSize];
+        in.readByteArray(mMessageBody);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mNanoAppId);
+        out.writeInt(mIsBroadcasted ? 1 : 0);
+        out.writeInt(mMessageType);
+
+        out.writeInt(mMessageBody.length);
+        out.writeByteArray(mMessageBody);
+    }
+
+    public static final Creator<NanoAppMessage> CREATOR =
+            new Creator<NanoAppMessage>() {
+                @Override
+                public NanoAppMessage createFromParcel(Parcel in) {
+                    return new NanoAppMessage(in);
+                }
+
+                @Override
+                public NanoAppMessage[] newArray(int size) {
+                    return new NanoAppMessage[size];
+                }
+            };
+}
diff --git a/android/hardware/location/NanoAppState.java b/android/hardware/location/NanoAppState.java
new file mode 100644
index 0000000..644031b
--- /dev/null
+++ b/android/hardware/location/NanoAppState.java
@@ -0,0 +1,88 @@
+/*
+ * 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.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class describing the nanoapp state information resulting from a query to a Context Hub.
+ *
+ * @hide
+ */
+public final class NanoAppState implements Parcelable {
+    private long mNanoAppId;
+    private int mNanoAppVersion;
+    private boolean mIsEnabled;
+
+    public NanoAppState(long nanoAppId, int appVersion, boolean enabled) {
+        mNanoAppId = nanoAppId;
+        mNanoAppVersion = appVersion;
+        mIsEnabled = enabled;
+    }
+
+    /**
+     * @return the NanoAppInfo for this app
+     */
+    public long getNanoAppId() {
+        return mNanoAppId;
+    }
+
+    /**
+     * @return the app version
+     */
+    public long getNanoAppVersion() {
+        return mNanoAppVersion;
+    }
+
+    /**
+     * @return {@code true} if the app is enabled at the Context Hub, {@code false} otherwise
+     */
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    private NanoAppState(Parcel in) {
+        mNanoAppId = in.readLong();
+        mNanoAppVersion = in.readInt();
+        mIsEnabled = (in.readInt() == 1);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mNanoAppId);
+        out.writeInt(mNanoAppVersion);
+        out.writeInt(mIsEnabled ? 1 : 0);
+    }
+
+    public static final Creator<NanoAppState> CREATOR =
+            new Creator<NanoAppState>() {
+                @Override
+                public NanoAppState createFromParcel(Parcel in) {
+                    return new NanoAppState(in);
+                }
+
+                @Override
+                public NanoAppState[] newArray(int size) {
+                    return new NanoAppState[size];
+                }
+            };
+}
diff --git a/android/hardware/usb/UsbManager.java b/android/hardware/usb/UsbManager.java
index 595d857..996824d 100644
--- a/android/hardware/usb/UsbManager.java
+++ b/android/hardware/usb/UsbManager.java
@@ -102,7 +102,7 @@
             "android.hardware.usb.action.USB_PORT_CHANGED";
 
    /**
-     * Broadcast Action:  A broadcast for USB device attached event.
+     * Activity intent sent when a USB device is attached.
      *
      * This intent is sent when a USB device is attached to the USB bus when in host mode.
      * <ul>
@@ -128,9 +128,8 @@
             "android.hardware.usb.action.USB_DEVICE_DETACHED";
 
    /**
-     * Broadcast Action:  A broadcast for USB accessory attached event.
+     * Activity intent sent when a USB accessory is attached.
      *
-     * This intent is sent when a USB accessory is attached.
      * <ul>
      * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory}
      * for the attached accessory
diff --git a/android/location/GnssClock.java b/android/location/GnssClock.java
index 25d247d..671c57c 100644
--- a/android/location/GnssClock.java
+++ b/android/location/GnssClock.java
@@ -332,6 +332,9 @@
     /**
      * Gets the clock's Drift in nanoseconds per second.
      *
+     * <p>This value is the instantaneous time-derivative of the value provided by
+     * {@link #getBiasNanos()}.
+     *
      * <p>A positive value indicates that the frequency is higher than the nominal (e.g. GPS master
      * clock) frequency. The error estimate for this reported drift is
      * {@link #getDriftUncertaintyNanosPerSecond()}.
diff --git a/android/net/NetworkCapabilities.java b/android/net/NetworkCapabilities.java
index 4bb8844..db12dd9 100644
--- a/android/net/NetworkCapabilities.java
+++ b/android/net/NetworkCapabilities.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,6 +24,8 @@
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.StringJoiner;
 
@@ -77,6 +80,31 @@
      */
     private long mNetworkCapabilities;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+            NET_CAPABILITY_MMS,
+            NET_CAPABILITY_SUPL,
+            NET_CAPABILITY_DUN,
+            NET_CAPABILITY_FOTA,
+            NET_CAPABILITY_IMS,
+            NET_CAPABILITY_CBS,
+            NET_CAPABILITY_WIFI_P2P,
+            NET_CAPABILITY_IA,
+            NET_CAPABILITY_RCS,
+            NET_CAPABILITY_XCAP,
+            NET_CAPABILITY_EIMS,
+            NET_CAPABILITY_NOT_METERED,
+            NET_CAPABILITY_INTERNET,
+            NET_CAPABILITY_NOT_RESTRICTED,
+            NET_CAPABILITY_TRUSTED,
+            NET_CAPABILITY_NOT_VPN,
+            NET_CAPABILITY_VALIDATED,
+            NET_CAPABILITY_CAPTIVE_PORTAL,
+            NET_CAPABILITY_FOREGROUND,
+    })
+    public @interface NetCapability { }
+
     /**
      * Indicates this is a network that has the ability to reach the
      * carrier's MMSC for sending and receiving MMS messages.
@@ -260,11 +288,11 @@
      * Multiple capabilities may be applied sequentially.  Note that when searching
      * for a network to satisfy a request, all capabilities requested must be satisfied.
      *
-     * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added.
+     * @param capability the capability to be added.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities addCapability(int capability) {
+    public NetworkCapabilities addCapability(@NetCapability int capability) {
         if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
             throw new IllegalArgumentException("NetworkCapability out of range");
         }
@@ -275,11 +303,11 @@
     /**
      * Removes (if found) the given capability from this {@code NetworkCapability} instance.
      *
-     * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed.
+     * @param capability the capability to be removed.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities removeCapability(int capability) {
+    public NetworkCapabilities removeCapability(@NetCapability int capability) {
         if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
             throw new IllegalArgumentException("NetworkCapability out of range");
         }
@@ -290,21 +318,20 @@
     /**
      * Gets all the capabilities set on this {@code NetworkCapability} instance.
      *
-     * @return an array of {@code NetworkCapabilities.NET_CAPABILITY_*} values
-     *         for this instance.
+     * @return an array of capability values for this instance.
      * @hide
      */
-    public int[] getCapabilities() {
+    public @NetCapability int[] getCapabilities() {
         return BitUtils.unpackBits(mNetworkCapabilities);
     }
 
     /**
      * Tests for the presence of a capabilitity on this instance.
      *
-     * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for.
+     * @param capability the capabilities to be tested for.
      * @return {@code true} if set on this instance.
      */
-    public boolean hasCapability(int capability) {
+    public boolean hasCapability(@NetCapability int capability) {
         if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
             return false;
         }
@@ -385,6 +412,19 @@
      */
     private long mTransportTypes;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "TRANSPORT_" }, value = {
+            TRANSPORT_CELLULAR,
+            TRANSPORT_WIFI,
+            TRANSPORT_BLUETOOTH,
+            TRANSPORT_ETHERNET,
+            TRANSPORT_VPN,
+            TRANSPORT_WIFI_AWARE,
+            TRANSPORT_LOWPAN,
+    })
+    public @interface Transport { }
+
     /**
      * Indicates this network uses a Cellular transport.
      */
@@ -426,7 +466,7 @@
     public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
 
     /** @hide */
-    public static boolean isValidTransport(int transportType) {
+    public static boolean isValidTransport(@Transport int transportType) {
         return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT);
     }
 
@@ -449,11 +489,11 @@
      * to be selected.  This is logically different than
      * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
      *
-     * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added.
+     * @param transportType the transport type to be added.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities addTransportType(int transportType) {
+    public NetworkCapabilities addTransportType(@Transport int transportType) {
         checkValidTransportType(transportType);
         mTransportTypes |= 1 << transportType;
         setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -463,11 +503,11 @@
     /**
      * Removes (if found) the given transport from this {@code NetworkCapability} instance.
      *
-     * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed.
+     * @param transportType the transport type to be removed.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities removeTransportType(int transportType) {
+    public NetworkCapabilities removeTransportType(@Transport int transportType) {
         checkValidTransportType(transportType);
         mTransportTypes &= ~(1 << transportType);
         setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -477,21 +517,20 @@
     /**
      * Gets all the transports set on this {@code NetworkCapability} instance.
      *
-     * @return an array of {@code NetworkCapabilities.TRANSPORT_*} values
-     *         for this instance.
+     * @return an array of transport type values for this instance.
      * @hide
      */
-    public int[] getTransportTypes() {
+    public @Transport int[] getTransportTypes() {
         return BitUtils.unpackBits(mTransportTypes);
     }
 
     /**
      * Tests for the presence of a transport on this instance.
      *
-     * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
+     * @param transportType the transport type to be tested for.
      * @return {@code true} if set on this instance.
      */
-    public boolean hasTransport(int transportType) {
+    public boolean hasTransport(@Transport int transportType) {
         return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0);
     }
 
@@ -896,7 +935,7 @@
     /**
      * @hide
      */
-    public static String capabilityNamesOf(int[] capabilities) {
+    public static String capabilityNamesOf(@NetCapability int[] capabilities) {
         StringJoiner joiner = new StringJoiner("|");
         if (capabilities != null) {
             for (int c : capabilities) {
@@ -909,7 +948,7 @@
     /**
      * @hide
      */
-    public static String capabilityNameOf(int capability) {
+    public static String capabilityNameOf(@NetCapability int capability) {
         switch (capability) {
             case NET_CAPABILITY_MMS:            return "MMS";
             case NET_CAPABILITY_SUPL:           return "SUPL";
@@ -937,7 +976,7 @@
     /**
      * @hide
      */
-    public static String transportNamesOf(int[] types) {
+    public static String transportNamesOf(@Transport int[] types) {
         StringJoiner joiner = new StringJoiner("|");
         if (types != null) {
             for (int t : types) {
@@ -950,14 +989,14 @@
     /**
      * @hide
      */
-    public static String transportNameOf(int transport) {
+    public static String transportNameOf(@Transport int transport) {
         if (!isValidTransport(transport)) {
             return "UNKNOWN";
         }
         return TRANSPORT_NAMES[transport];
     }
 
-    private static void checkValidTransportType(int transport) {
+    private static void checkValidTransportType(@Transport int transport) {
         Preconditions.checkArgument(
                 isValidTransport(transport), "Invalid TransportType " + transport);
     }
diff --git a/android/net/NetworkRequest.java b/android/net/NetworkRequest.java
index 95a8bb4..25b1705 100644
--- a/android/net/NetworkRequest.java
+++ b/android/net/NetworkRequest.java
@@ -155,14 +155,13 @@
          * Add the given capability requirement to this builder.  These represent
          * the requested network's required capabilities.  Note that when searching
          * for a network to satisfy a request, all capabilities requested must be
-         * satisfied.  See {@link NetworkCapabilities} for {@code NET_CAPABILITY_*}
-         * definitions.
+         * satisfied.
          *
-         * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add.
+         * @param capability The capability to add.
          * @return The builder to facilitate chaining
          *         {@code builder.addCapability(...).addCapability();}.
          */
-        public Builder addCapability(int capability) {
+        public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.addCapability(capability);
             return this;
         }
@@ -170,10 +169,10 @@
         /**
          * Removes (if found) the given capability from this builder instance.
          *
-         * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to remove.
+         * @param capability The capability to remove.
          * @return The builder to facilitate chaining.
          */
-        public Builder removeCapability(int capability) {
+        public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.removeCapability(capability);
             return this;
         }
@@ -208,13 +207,12 @@
          * Adds the given transport requirement to this builder.  These represent
          * the set of allowed transports for the request.  Only networks using one
          * of these transports will satisfy the request.  If no particular transports
-         * are required, none should be specified here.  See {@link NetworkCapabilities}
-         * for {@code TRANSPORT_*} definitions.
+         * are required, none should be specified here.
          *
-         * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to add.
+         * @param transportType The transport type to add.
          * @return The builder to facilitate chaining.
          */
-        public Builder addTransportType(int transportType) {
+        public Builder addTransportType(@NetworkCapabilities.Transport int transportType) {
             mNetworkCapabilities.addTransportType(transportType);
             return this;
         }
@@ -222,10 +220,10 @@
         /**
          * Removes (if found) the given transport from this builder instance.
          *
-         * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to remove.
+         * @param transportType The transport type to remove.
          * @return The builder to facilitate chaining.
          */
-        public Builder removeTransportType(int transportType) {
+        public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) {
             mNetworkCapabilities.removeTransportType(transportType);
             return this;
         }
diff --git a/android/net/metrics/DefaultNetworkEvent.java b/android/net/metrics/DefaultNetworkEvent.java
index 28cf42f..eb61c15 100644
--- a/android/net/metrics/DefaultNetworkEvent.java
+++ b/android/net/metrics/DefaultNetworkEvent.java
@@ -16,73 +16,43 @@
 
 package android.net.metrics;
 
+import static android.net.ConnectivityManager.NETID_UNSET;
+
 import android.net.NetworkCapabilities;
-import android.os.Parcel;
-import android.os.Parcelable;
 
 /**
  * An event recorded by ConnectivityService when there is a change in the default network.
  * {@hide}
  */
-public final class DefaultNetworkEvent implements Parcelable {
+public class DefaultNetworkEvent {
+
     // The ID of the network that has become the new default or NETID_UNSET if none.
-    public final int netId;
+    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 final int[] transportTypes;
+    public int[] transportTypes = new int[0];
     // The ID of the network that was the default before or NETID_UNSET if none.
-    public final int prevNetId;
+    public int prevNetId = NETID_UNSET;
     // Whether the previous network had IPv4/IPv6 connectivity.
-    public final boolean prevIPv4;
-    public final boolean prevIPv6;
-
-    public DefaultNetworkEvent(int netId, int[] transportTypes,
-                int prevNetId, boolean prevIPv4, boolean prevIPv6) {
-        this.netId = netId;
-        this.transportTypes = transportTypes;
-        this.prevNetId = prevNetId;
-        this.prevIPv4 = prevIPv4;
-        this.prevIPv6 = prevIPv6;
-    }
-
-    private DefaultNetworkEvent(Parcel in) {
-        this.netId = in.readInt();
-        this.transportTypes = in.createIntArray();
-        this.prevNetId = in.readInt();
-        this.prevIPv4 = (in.readByte() > 0);
-        this.prevIPv6 = (in.readByte() > 0);
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(netId);
-        out.writeIntArray(transportTypes);
-        out.writeInt(prevNetId);
-        out.writeByte(prevIPv4 ? (byte) 1 : (byte) 0);
-        out.writeByte(prevIPv6 ? (byte) 1 : (byte) 0);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
+    public boolean prevIPv4;
+    public boolean prevIPv6;
 
     @Override
     public String toString() {
-      String prevNetwork = String.valueOf(prevNetId);
-      String newNetwork = String.valueOf(netId);
-      if (prevNetId != 0) {
-          prevNetwork += ":" + ipSupport();
-      }
-      if (netId != 0) {
-          newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
-      }
-      return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+        String prevNetwork = String.valueOf(prevNetId);
+        String newNetwork = String.valueOf(netId);
+        if (prevNetId != 0) {
+            prevNetwork += ":" + ipSupport();
+        }
+        if (netId != 0) {
+            newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+        }
+        return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
     }
 
     private String ipSupport() {
         if (prevIPv4 && prevIPv6) {
-            return "DUAL";
+            return "IPv4v6";
         }
         if (prevIPv6) {
             return "IPv6";
@@ -92,15 +62,4 @@
         }
         return "NONE";
     }
-
-    public static final Parcelable.Creator<DefaultNetworkEvent> CREATOR
-        = new Parcelable.Creator<DefaultNetworkEvent>() {
-        public DefaultNetworkEvent createFromParcel(Parcel in) {
-            return new DefaultNetworkEvent(in);
-        }
-
-        public DefaultNetworkEvent[] newArray(int size) {
-            return new DefaultNetworkEvent[size];
-        }
-    };
 }
diff --git a/android/net/util/VersionedBroadcastListener.java b/android/net/util/VersionedBroadcastListener.java
new file mode 100644
index 0000000..107c404
--- /dev/null
+++ b/android/net/util/VersionedBroadcastListener.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+
+/**
+ * A utility class that runs the provided callback on the provided handler when
+ * intents matching the provided filter arrive. Intents received by a stale
+ * receiver are safely ignored.
+ *
+ * Calls to startListening() and stopListening() must happen on the same thread.
+ *
+ * @hide
+ */
+public class VersionedBroadcastListener {
+    private static final boolean DBG = false;
+
+    public interface IntentCallback {
+        public void run(Intent intent);
+    }
+
+    private final String mTag;
+    private final Context mContext;
+    private final Handler mHandler;
+    private final IntentFilter mFilter;
+    private final Consumer<Intent> mCallback;
+    private final AtomicInteger mGenerationNumber;
+    private BroadcastReceiver mReceiver;
+
+    public VersionedBroadcastListener(String tag, Context ctx, Handler handler,
+            IntentFilter filter, Consumer<Intent> callback) {
+        mTag = tag;
+        mContext = ctx;
+        mHandler = handler;
+        mFilter = filter;
+        mCallback = callback;
+        mGenerationNumber = new AtomicInteger(0);
+    }
+
+    public void startListening() {
+        if (DBG) Log.d(mTag, "startListening");
+        if (mReceiver != null) return;
+
+        mReceiver = new Receiver(mTag, mGenerationNumber, mCallback);
+        mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
+    }
+
+    public void stopListening() {
+        if (DBG) Log.d(mTag, "stopListening");
+        if (mReceiver == null) return;
+
+        mGenerationNumber.incrementAndGet();
+        mContext.unregisterReceiver(mReceiver);
+        mReceiver = null;
+    }
+
+    private static class Receiver extends BroadcastReceiver {
+        public final String tag;
+        public final AtomicInteger atomicGenerationNumber;
+        public final Consumer<Intent> callback;
+        // Used to verify this receiver is still current.
+        public final int generationNumber;
+
+        public Receiver(
+                String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
+            this.tag = tag;
+            this.atomicGenerationNumber = atomicGenerationNumber;
+            this.callback = callback;
+            generationNumber = atomicGenerationNumber.incrementAndGet();
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int currentGenerationNumber = atomicGenerationNumber.get();
+
+            if (DBG) {
+                Log.d(tag, "receiver generationNumber=" + generationNumber +
+                        ", current generationNumber=" + currentGenerationNumber);
+            }
+            if (generationNumber != currentGenerationNumber) return;
+
+            callback.accept(intent);
+        }
+    }
+}
diff --git a/android/net/wifi/WifiManager.java b/android/net/wifi/WifiManager.java
index 649b0ce..c2959d5 100644
--- a/android/net/wifi/WifiManager.java
+++ b/android/net/wifi/WifiManager.java
@@ -68,6 +68,7 @@
  * leaks within the calling process.
  * <p>
  * It deals with several categories of items:
+ * </p>
  * <ul>
  * <li>The list of configured networks. The list can be viewed and updated, and
  * attributes of individual entries can be modified.</li>
@@ -79,9 +80,11 @@
  * <li>It defines the names of various Intent actions that are broadcast upon
  * any sort of change in Wi-Fi state.
  * </ul>
+ * <p>
  * This is the API to use when performing Wi-Fi specific operations. To perform
  * operations that pertain to network connectivity at an abstract level, use
  * {@link android.net.ConnectivityManager}.
+ * </p>
  */
 @SystemService(Context.WIFI_SERVICE)
 public class WifiManager {
@@ -1575,7 +1578,21 @@
      * Request a scan for access points. Returns immediately. The availability
      * of the results is made known later by means of an asynchronous event sent
      * on completion of the scan.
-     * @return {@code true} if the operation succeeded, i.e., the scan was initiated
+     * <p>
+     * To initiate a Wi-Fi scan, declare the
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+     * permission in the manifest, and perform these steps:
+     * </p>
+     * <ol style="1">
+     * <li>Invoke the following method:
+     * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).startScan()}</li>
+     * <li>
+     * Register a BroadcastReceiver to listen to
+     * {@code SCAN_RESULTS_AVAILABLE_ACTION}.</li>
+     * <li>When a broadcast is received, call:
+     * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li>
+     * </ol>
+     * @return {@code true} if the operation succeeded, i.e., the scan was initiated.
      */
     public boolean startScan() {
         return startScan(null);
@@ -1877,32 +1894,6 @@
     }
 
     /**
-     * This call is deprecated and removed.  It is no longer used to
-     * start WiFi Tethering.  Please use {@link ConnectivityManager#startTethering(int, boolean,
-     * ConnectivityManager#OnStartTetheringCallback)} if
-     * the caller has proper permissions.  Callers can also use the LocalOnlyHotspot feature for a
-     * hotspot capable of communicating with co-located devices {@link
-     * WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)}.
-     *
-     * @param wifiConfig SSID, security and channel details as
-     *        part of WifiConfiguration
-     * @return {@code false}
-     *
-     * @hide
-     * @deprecated This API is nolonger supported.
-     * @removed
-     */
-    @SystemApi
-    @Deprecated
-    @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
-    public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
-        String packageName = mContext.getOpPackageName();
-
-        Log.w(TAG, packageName + " attempted call to setWifiApEnabled: enabled = " + enabled);
-        return false;
-    }
-
-    /**
      * Call allowing ConnectivityService to update WifiService with interface mode changes.
      *
      * The possible modes include: {@link IFACE_IP_MODE_TETHERED},
diff --git a/android/os/BatteryStats.java b/android/os/BatteryStats.java
index 8682c01..a8bd940 100644
--- a/android/os/BatteryStats.java
+++ b/android/os/BatteryStats.java
@@ -1986,7 +1986,7 @@
     public abstract long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which);
 
     /**
-     * Returns the number of times that the devie has started idling.
+     * Returns the number of times that the device has started idling.
      *
      * {@hide}
      */
@@ -6453,7 +6453,7 @@
                 pw.println();
             }
         }
-        if (!filtering || (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0) {
+        if (!filtering || (flags & DUMP_DAILY_ONLY) != 0) {
             pw.println("Daily stats:");
             pw.print("  Current start time: ");
             pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
diff --git a/android/os/Binder.java b/android/os/Binder.java
index e9e695b..2bfb013 100644
--- a/android/os/Binder.java
+++ b/android/os/Binder.java
@@ -27,12 +27,14 @@
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
 
 import libcore.io.IoUtils;
+import libcore.util.NativeAllocationRegistry;
 
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Modifier;
+import java.util.ArrayList;
 
 /**
  * Base class for a remotable object, the core part of a lightweight
@@ -90,6 +92,20 @@
      */
     private static volatile TransactionTracker sTransactionTracker = null;
 
+    /**
+     * Guestimate of native memory associated with a Binder.
+     */
+    private static final int NATIVE_ALLOCATION_SIZE = 500;
+
+    private static native long getNativeFinalizer();
+
+    // Use a Holder to allow static initialization of Binder in the boot image, and
+    // possibly to avoid some initialization ordering issues.
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Binder.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+    }
+
     // Transaction tracking code.
 
     /**
@@ -167,7 +183,7 @@
         try {
             if (binder instanceof BinderProxy) {
                 ((BinderProxy) binder).mWarnOnBlocking = false;
-            } else if (binder != null
+            } else if (binder != null && binder.getInterfaceDescriptor() != null
                     && binder.queryLocalInterface(binder.getInterfaceDescriptor()) == null) {
                 Log.w(TAG, "Unable to allow blocking on interface " + binder);
             }
@@ -188,8 +204,11 @@
         }
     }
 
-    /* mObject is used by native code, do not remove or rename */
-    private long mObject;
+    /**
+     * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
+     */
+    private final long mObject;
+
     private IInterface mOwner;
     private String mDescriptor;
 
@@ -360,7 +379,8 @@
      * Default constructor initializes the object.
      */
     public Binder() {
-        init();
+        mObject = getNativeBBinderHolder();
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);
 
         if (FIND_POTENTIAL_LEAKS) {
             final Class<? extends Binder> klass = getClass();
@@ -414,7 +434,7 @@
      * descriptor.
      */
     public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
-        if (mDescriptor.equals(descriptor)) {
+        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
             return mOwner;
         }
         return null;
@@ -643,14 +663,6 @@
         return true;
     }
 
-    protected void finalize() throws Throwable {
-        try {
-            destroyBinder();
-        } finally {
-            super.finalize();
-        }
-    }
-
     static void checkParcel(IBinder obj, int code, Parcel parcel, String msg) {
         if (CHECK_PARCEL_SIZE && parcel.dataSize() >= 800*1024) {
             // Trying to send > 800k, this is way too much
@@ -674,8 +686,8 @@
         }
     }
 
-    private native final void init();
-    private native final void destroyBinder();
+    private static native long getNativeBBinderHolder();
+    private static native long getFinalizer();
 
     // Entry point from android_util_Binder.cpp's onTransact
     private boolean execTransact(int code, long dataObj, long replyObj,
@@ -736,11 +748,207 @@
  */
 final class BinderProxy implements IBinder {
     // See android_util_Binder.cpp for the native half of this.
-    // TODO: Consider using NativeAllocationRegistry instead of finalization.
 
     // Assume the process-wide default value when created
     volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
 
+    /*
+     * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
+     * We roll our own only because we need to lazily remove WeakReferences during accesses
+     * to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable
+     * because we want weak values, not keys.
+     * Our hash table is never resized, but the number of entries is unlimited;
+     * performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE.
+     * Not thread-safe. Client ensures there's a single access at a time.
+     */
+    private static final class ProxyMap {
+        private static final int LOG_MAIN_INDEX_SIZE = 8;
+        private static final int MAIN_INDEX_SIZE = 1 <<  LOG_MAIN_INDEX_SIZE;
+        private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
+
+        /**
+         * We next warn when we exceed this bucket size.
+         */
+        private int mWarnBucketSize = 20;
+
+        /**
+         * Increment mWarnBucketSize by WARN_INCREMENT each time we warn.
+         */
+        private static final int WARN_INCREMENT = 10;
+
+        /**
+         * Hash function tailored to native pointers.
+         * Returns a value < MAIN_INDEX_SIZE.
+         */
+        private static int hash(long arg) {
+            return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK;
+        }
+
+        /**
+         * Return the total number of pairs in the map.
+         */
+        int size() {
+            int size = 0;
+            for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+                if (a != null) {
+                    size += a.size();
+                }
+            }
+            return size;
+        }
+
+        /**
+         * Remove ith entry from the hash bucket indicated by hash.
+         */
+        private void remove(int hash, int index) {
+            Long[] keyArray = mMainIndexKeys[hash];
+            ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash];
+            int size = valueArray.size();  // KeyArray may have extra elements.
+            // Move last entry into empty slot, and truncate at end.
+            if (index != size - 1) {
+                keyArray[index] = keyArray[size - 1];
+                valueArray.set(index, valueArray.get(size - 1));
+            }
+            valueArray.remove(size - 1);
+            // Just leave key array entry; it's unused. We only trust the valueArray size.
+        }
+
+        /**
+         * Look up the supplied key. If we have a non-cleared entry for it, return it.
+         */
+        BinderProxy get(long key) {
+            int myHash = hash(key);
+            Long[] keyArray = mMainIndexKeys[myHash];
+            if (keyArray == null) {
+                return null;
+            }
+            ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+            int bucketSize = valueArray.size();
+            for (int i = 0; i < bucketSize; ++i) {
+                long foundKey = keyArray[i];
+                if (key == foundKey) {
+                    WeakReference<BinderProxy> wr = valueArray.get(i);
+                    BinderProxy bp = wr.get();
+                    if (bp != null) {
+                        return bp;
+                    } else {
+                        remove(myHash, i);
+                        return null;
+                    }
+                }
+            }
+            return null;
+        }
+
+        private int mRandom;  // A counter used to generate a "random" index. World's 2nd worst RNG.
+
+        /**
+         * Add the key-value pair to the map.
+         * Requires that the indicated key is not already in the map.
+         */
+        void set(long key, @NonNull BinderProxy value) {
+            int myHash = hash(key);
+            ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+            if (valueArray == null) {
+                valueArray = mMainIndexValues[myHash] = new ArrayList<>();
+                mMainIndexKeys[myHash] = new Long[1];
+            }
+            int size = valueArray.size();
+            WeakReference<BinderProxy> newWr = new WeakReference<>(value);
+            // First look for a cleared reference.
+            // This ensures that ArrayList size is bounded by the maximum occupancy of
+            // that bucket.
+            for (int i = 0; i < size; ++i) {
+                if (valueArray.get(i).get() == null) {
+                    valueArray.set(i, newWr);
+                    Long[] keyArray = mMainIndexKeys[myHash];
+                    keyArray[i] = key;
+                    if (i < size - 1) {
+                        // "Randomly" check one of the remaining entries in [i+1, size), so that
+                        // needlessly long buckets are eventually pruned.
+                        int rnd = Math.floorMod(++mRandom, size - (i + 1));
+                        if (valueArray.get(i + 1 + rnd).get() == null) {
+                            remove(myHash, i + 1 + rnd);
+                        }
+                    }
+                    return;
+                }
+            }
+            valueArray.add(size, newWr);
+            Long[] keyArray = mMainIndexKeys[myHash];
+            if (keyArray.length == size) {
+                // size >= 1, since we initially allocated one element
+                Long[] newArray = new Long[size + size / 2 + 2];
+                System.arraycopy(keyArray, 0, newArray, 0, size);
+                newArray[size] = key;
+                mMainIndexKeys[myHash] = newArray;
+            } else {
+                keyArray[size] = key;
+            }
+            if (size >= mWarnBucketSize) {
+                Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
+                        + " total = " + size());
+                mWarnBucketSize += WARN_INCREMENT;
+            }
+        }
+
+        // Corresponding ArrayLists in the following two arrays always have the same size.
+        // They contain no empty entries. However WeakReferences in the values ArrayLists
+        // may have been cleared.
+
+        // mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) .
+        // The values ArrayList has the proper size(), the corresponding keys array
+        // is always at least the same size, but may be larger.
+        // If either a particular keys array, or the corresponding values ArrayList
+        // are null, then they both are.
+        private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][];
+        private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues =
+                new ArrayList[MAIN_INDEX_SIZE];
+    }
+
+    private static ProxyMap sProxyMap = new ProxyMap();
+
+    /**
+     * Return a BinderProxy for IBinder.
+     * This method is thread-hostile!  The (native) caller serializes getInstance() calls using
+     * gProxyLock.
+     * If we previously returned a BinderProxy bp for the same iBinder, and bp is still
+     * in use, then we return the same bp.
+     *
+     * @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData.
+     * Takes ownership of nativeData iff <result>.mNativeData == nativeData.  Caller will usually
+     * delete nativeData if that's not the case.
+     * @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object.
+     */
+    private static BinderProxy getInstance(long nativeData, long iBinder) {
+        BinderProxy result = sProxyMap.get(iBinder);
+        if (result == null) {
+            result = new BinderProxy(nativeData);
+            sProxyMap.set(iBinder, result);
+        }
+        return result;
+    }
+
+    private BinderProxy(long nativeData) {
+        mNativeData = nativeData;
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeData);
+    }
+
+    /**
+     * Guestimate of native memory associated with a BinderProxy.
+     * This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector
+     * that points back to us. We guess high since it includes a GlobalRef, which
+     * may be in short supply.
+     */
+    private static final int NATIVE_ALLOCATION_SIZE = 1000;
+
+    // Use a Holder to allow static initialization of BinderProxy in the boot image, and
+    // to avoid some initialization ordering issues.
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                BinderProxy.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+    }
+
     public native boolean pingBinder();
     public native boolean isBinderAlive();
 
@@ -776,6 +984,7 @@
         }
     }
 
+    private static native long getNativeFinalizer();
     public native String getInterfaceDescriptor() throws RemoteException;
     public native boolean transactNative(int code, Parcel data, Parcel reply,
             int flags) throws RemoteException;
@@ -830,21 +1039,6 @@
         }
     }
 
-    BinderProxy() {
-        mSelf = new WeakReference(this);
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            destroy();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private native final void destroy();
-
     private static final void sendDeathNotice(DeathRecipient recipient) {
         if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
         try {
@@ -856,19 +1050,9 @@
         }
     }
 
-    // This WeakReference to "this" is used only by native code to "attach" to the
-    // native IBinder object.
-    // Using WeakGlobalRefs instead currently appears unsafe, in that they can yield a
-    // non-null value after the BinderProxy is enqueued for finalization.
-    // Used only once immediately after construction.
-    // TODO: Consider making the extra native-to-java call to compute this on the fly.
-    final private WeakReference mSelf;
-
-    // Native pointer to the wrapped native IBinder object. Counted as strong reference.
-    private long mObject;
-
-    // Native pointer to native DeathRecipientList. Counted as strong reference.
-    // Basically owned by the JavaProxy object. Reference counted only because DeathRecipients
-    // hold a weak reference that can be temporarily promoted.
-    private long mOrgue;
+    /**
+     * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
+     * native IBinder object, and a DeathRecipientList.
+     */
+    private final long mNativeData;
 }
diff --git a/android/os/IServiceManager.java b/android/os/IServiceManager.java
index 87c65ec..2176a78 100644
--- a/android/os/IServiceManager.java
+++ b/android/os/IServiceManager.java
@@ -45,13 +45,13 @@
      * Place a new @a service called @a name into the service
      * manager.
      */
-    void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
-                throws RemoteException;
+    void addService(String name, IBinder service, boolean allowIsolated, int dumpFlags)
+            throws RemoteException;
 
     /**
      * Return a list of all currently running services.
      */
-    String[] listServices(int dumpPriority) throws RemoteException;
+    String[] listServices(int dumpFlags) throws RemoteException;
 
     /**
      * Assign a permission controller to the service manager.  After set, this
@@ -72,9 +72,13 @@
     /*
      * Must update values in IServiceManager.h
      */
-    int DUMP_PRIORITY_CRITICAL = 1 << 0;
-    int DUMP_PRIORITY_HIGH = 1 << 1;
-    int DUMP_PRIORITY_NORMAL = 1 << 2;
-    int DUMP_PRIORITY_ALL = DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_HIGH
-            | DUMP_PRIORITY_NORMAL;
+    /* Allows services to dump sections according to priorities. */
+    int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
+    int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
+    int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
+    int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
+            | DUMP_FLAG_PRIORITY_NORMAL;
+    /* Allows services to dump sections in protobuf format. */
+    int DUMP_FLAG_PROTO = 1 << 3;
+
 }
diff --git a/android/os/Parcel.java b/android/os/Parcel.java
index 857e8a6..c2cf396 100644
--- a/android/os/Parcel.java
+++ b/android/os/Parcel.java
@@ -561,6 +561,22 @@
         mClassCookies = from.mClassCookies;
     }
 
+    /** @hide */
+    public Map<Class, Object> copyClassCookies() {
+        return new ArrayMap<>(mClassCookies);
+    }
+
+    /** @hide */
+    public void putClassCookies(Map<Class, Object> cookies) {
+        if (cookies == null) {
+            return;
+        }
+        if (mClassCookies == null) {
+            mClassCookies = new ArrayMap<>();
+        }
+        mClassCookies.putAll(cookies);
+    }
+
     /**
      * Report whether the parcel contains any marshalled file descriptors.
      */
diff --git a/android/os/ServiceManager.java b/android/os/ServiceManager.java
index 34c7845..42ec315 100644
--- a/android/os/ServiceManager.java
+++ b/android/os/ServiceManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2007 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,9 +16,29 @@
 
 package android.os;
 
+import android.util.Log;
+
+import com.android.internal.os.BinderInternal;
+
+import java.util.HashMap;
 import java.util.Map;
 
+/** @hide */
 public final class ServiceManager {
+    private static final String TAG = "ServiceManager";
+    private static IServiceManager sServiceManager;
+    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
+
+    private static IServiceManager getIServiceManager() {
+        if (sServiceManager != null) {
+            return sServiceManager;
+        }
+
+        // Find the service manager
+        sServiceManager = ServiceManagerNative
+                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
+        return sServiceManager;
+    }
 
     /**
      * Returns a reference to a service with the given name.
@@ -27,14 +47,32 @@
      * @return a reference to the service, or <code>null</code> if the service doesn't exist
      */
     public static IBinder getService(String name) {
+        try {
+            IBinder service = sCache.get(name);
+            if (service != null) {
+                return service;
+            } else {
+                return Binder.allowBlocking(getIServiceManager().getService(name));
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in getService", e);
+        }
         return null;
     }
 
     /**
-     * Is not supposed to return null, but that is fine for layoutlib.
+     * Returns a reference to a service with the given name, or throws
+     * {@link NullPointerException} if none is found.
+     *
+     * @hide
      */
     public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
-        throw new ServiceNotFoundException(name);
+        final IBinder binder = getService(name);
+        if (binder != null) {
+            return binder;
+        } else {
+            throw new ServiceNotFoundException(name);
+        }
     }
 
     /**
@@ -45,7 +83,39 @@
      * @param service the service object
      */
     public static void addService(String name, IBinder service) {
-        // pass
+        addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
+    }
+
+    /**
+     * Place a new @a service called @a name into the service
+     * manager.
+     *
+     * @param name the name of the new service
+     * @param service the service object
+     * @param allowIsolated set to true to allow isolated sandboxed processes
+     * to access this service
+     */
+    public static void addService(String name, IBinder service, boolean allowIsolated) {
+        addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
+    }
+
+    /**
+     * Place a new @a service called @a name into the service
+     * manager.
+     *
+     * @param name the name of the new service
+     * @param service the service object
+     * @param allowIsolated set to true to allow isolated sandboxed processes
+     * @param dumpPriority supported dump priority levels as a bitmask
+     * to access this service
+     */
+    public static void addService(String name, IBinder service, boolean allowIsolated,
+            int dumpPriority) {
+        try {
+            getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in addService", e);
+        }
     }
 
     /**
@@ -53,7 +123,17 @@
      * service manager.  Non-blocking.
      */
     public static IBinder checkService(String name) {
-        return null;
+        try {
+            IBinder service = sCache.get(name);
+            if (service != null) {
+                return service;
+            } else {
+                return Binder.allowBlocking(getIServiceManager().checkService(name));
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in checkService", e);
+            return null;
+        }
     }
 
     /**
@@ -62,9 +142,12 @@
      * case of an exception
      */
     public static String[] listServices() {
-        // actual implementation returns null sometimes, so it's ok
-        // to return null instead of an empty list.
-        return null;
+        try {
+            return getIServiceManager().listServices(IServiceManager.DUMP_FLAG_PRIORITY_ALL);
+        } catch (RemoteException e) {
+            Log.e(TAG, "error in listServices", e);
+            return null;
+        }
     }
 
     /**
@@ -76,7 +159,10 @@
      * @hide
      */
     public static void initServiceCache(Map<String, IBinder> cache) {
-        // pass
+        if (sCache.size() != 0) {
+            throw new IllegalStateException("setServiceCache may only be called once");
+        }
+        sCache.putAll(cache);
     }
 
     /**
@@ -87,7 +173,6 @@
      * @hide
      */
     public static class ServiceNotFoundException extends Exception {
-        // identical to the original implementation
         public ServiceNotFoundException(String name) {
             super("No service published for: " + name);
         }
diff --git a/android/os/ShellCommand.java b/android/os/ShellCommand.java
index e4a12e8..6223235 100644
--- a/android/os/ShellCommand.java
+++ b/android/os/ShellCommand.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.util.Slog;
+
 import com.android.internal.util.FastPrintWriter;
 
 import java.io.BufferedInputStream;
@@ -118,13 +119,33 @@
                 mErrPrintWriter.flush();
             }
             if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
-            mResultReceiver.send(res, null);
+            if (mResultReceiver != null) {
+                mResultReceiver.send(res, null);
+            }
         }
         if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
         return res;
     }
 
     /**
+     * Adopt the ResultReceiver that was given to this shell command from it, taking
+     * it over.  Primarily used to dispatch to another shell command.  Once called,
+     * this shell command will no longer return its own result when done.
+     */
+    public ResultReceiver adoptResultReceiver() {
+        ResultReceiver rr = mResultReceiver;
+        mResultReceiver = null;
+        return rr;
+    }
+
+    /**
+     * Return the raw FileDescriptor for the output stream.
+     */
+    public FileDescriptor getOutFileDescriptor() {
+        return mOut;
+    }
+
+    /**
      * Return direct raw access (not buffered) to the command's output data stream.
      */
     public OutputStream getRawOutputStream() {
@@ -145,6 +166,13 @@
     }
 
     /**
+     * Return the raw FileDescriptor for the error stream.
+     */
+    public FileDescriptor getErrFileDescriptor() {
+        return mErr;
+    }
+
+    /**
      * Return direct raw access (not buffered) to the command's error output data stream.
      */
     public OutputStream getRawErrorStream() {
@@ -168,6 +196,13 @@
     }
 
     /**
+     * Return the raw FileDescriptor for the input stream.
+     */
+    public FileDescriptor getInFileDescriptor() {
+        return mIn;
+    }
+
+    /**
      * Return direct raw access (not buffered) to the command's input data stream.
      */
     public InputStream getRawInputStream() {
diff --git a/android/os/StrictMode.java b/android/os/StrictMode.java
index 826ec1e..ee3e5bc 100644
--- a/android/os/StrictMode.java
+++ b/android/os/StrictMode.java
@@ -20,7 +20,6 @@
 import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
-import android.app.ApplicationErrorReport;
 import android.app.IActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -35,6 +34,7 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.HexDump;
@@ -48,8 +48,10 @@
 import java.io.StringWriter;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -352,8 +354,8 @@
                 } else {
                     msg = "StrictMode policy violation:";
                 }
-                if (info.crashInfo != null) {
-                    Log.d(TAG, msg + " " + info.crashInfo.stackTrace);
+                if (info.hasStackTrace()) {
+                    Log.d(TAG, msg + " " + info.getStackTrace());
                 } else {
                     Log.d(TAG, msg + " missing stack trace!");
                 }
@@ -1247,28 +1249,6 @@
         }
     }
 
-    /** Like parsePolicyFromMessage(), but returns the violation. */
-    private static int parseViolationFromMessage(String message) {
-        if (message == null) {
-            return 0;
-        }
-        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 {
-            return Integer.parseInt(violationString);
-        } catch (NumberFormatException e) {
-            return 0;
-        }
-    }
-
     private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
             new ThreadLocal<ArrayList<ViolationInfo>>() {
                 @Override
@@ -1516,7 +1496,7 @@
         // 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.crashInfo == null || info.crashInfo.stackTrace == null) {
+            if (info == null || !info.hasStackTrace()) {
                 Log.wtf(TAG, "unexpected null stacktrace");
                 return;
             }
@@ -1530,7 +1510,7 @@
                     gatheredViolations.set(violations);
                 }
                 for (ViolationInfo previous : violations) {
-                    if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) {
+                    if (info.getStackTrace().equals(previous.getStackTrace())) {
                         // Duplicate. Don't log.
                         return;
                     }
@@ -1576,8 +1556,7 @@
             }
 
             if (violationMaskSubset != 0) {
-                int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
-                violationMaskSubset |= violationBit;
+                violationMaskSubset |= info.getViolationBit();
                 final int savedPolicyMask = getThreadPolicyMask();
 
                 final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
@@ -1622,8 +1601,7 @@
     }
 
     private static void executeDeathPenalty(ViolationInfo info) {
-        int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
-        throw new StrictModeViolation(info.policy, violationBit, null);
+        throw new StrictModeViolation(info.policy, info.getViolationBit(), null);
     }
 
     /**
@@ -1670,7 +1648,7 @@
 
     private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
         public void report(String message, Throwable allocationSite) {
-            onVmPolicyViolation(message, allocationSite);
+            onVmPolicyViolation(allocationSite);
         }
     }
 
@@ -1709,7 +1687,7 @@
             long instances = instanceCounts[i];
             if (instances > limit) {
                 Throwable tr = new InstanceCountViolation(klass, instances, limit);
-                onVmPolicyViolation(tr.getMessage(), tr);
+                onVmPolicyViolation(tr);
             }
         }
     }
@@ -1833,22 +1811,24 @@
 
     /** @hide */
     public static void onSqliteObjectLeaked(String message, Throwable originStack) {
-        onVmPolicyViolation(message, originStack);
+        Throwable t = new Throwable(message);
+        t.setStackTrace(originStack.getStackTrace());
+        onVmPolicyViolation(t);
     }
 
     /** @hide */
     public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
-        onVmPolicyViolation(null, originStack);
+        onVmPolicyViolation(originStack);
     }
 
     /** @hide */
     public static void onIntentReceiverLeaked(Throwable originStack) {
-        onVmPolicyViolation(null, originStack);
+        onVmPolicyViolation(originStack);
     }
 
     /** @hide */
     public static void onServiceConnectionLeaked(Throwable originStack) {
-        onVmPolicyViolation(null, originStack);
+        onVmPolicyViolation(originStack);
     }
 
     /** @hide */
@@ -1857,7 +1837,7 @@
         if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
             throw new FileUriExposedException(message);
         } else {
-            onVmPolicyViolation(null, new Throwable(message));
+            onVmPolicyViolation(new Throwable(message));
         }
     }
 
@@ -1869,7 +1849,7 @@
                         + location
                         + " without permission grant flags; did you forget"
                         + " FLAG_GRANT_READ_URI_PERMISSION?";
-        onVmPolicyViolation(null, new Throwable(message));
+        onVmPolicyViolation(new Throwable(message));
     }
 
     /** @hide */
@@ -1899,10 +1879,9 @@
             } catch (UnknownHostException ignored) {
             }
         }
-
+        msg += HexDump.dumpHexString(firstPacket).trim() + " ";
         final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
-        onVmPolicyViolation(
-                HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg), forceDeath);
+        onVmPolicyViolation(new Throwable(msg), forceDeath);
     }
 
     /** @hide */
@@ -1912,24 +1891,23 @@
 
     /** @hide */
     public static void onUntaggedSocket() {
-        onVmPolicyViolation(null, new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
+        onVmPolicyViolation(new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
     }
 
     // Map from VM violation fingerprint to uptime millis.
     private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
 
     /** @hide */
-    public static void onVmPolicyViolation(String message, Throwable originStack) {
-        onVmPolicyViolation(message, originStack, false);
+    public static void onVmPolicyViolation(Throwable originStack) {
+        onVmPolicyViolation(originStack, false);
     }
 
     /** @hide */
-    public static void onVmPolicyViolation(
-            String message, Throwable originStack, boolean forceDeath) {
+    public static void onVmPolicyViolation(Throwable originStack, 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(message, originStack, sVmPolicy.mask);
+        final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
 
         // Erase stuff not relevant for process-wide violations
         info.numAnimationsRunning = 0;
@@ -2027,21 +2005,14 @@
      * read back all the encoded violations.
      */
     /* package */ static void readAndHandleBinderCallViolations(Parcel p) {
-        // Our own stack trace to append
-        StringWriter sw = new StringWriter();
-        sw.append("# via Binder call with stack:\n");
-        PrintWriter pw = new FastPrintWriter(sw, false, 256);
-        new LogStackTrace().printStackTrace(pw);
-        pw.flush();
-        String ourStack = sw.toString();
-
+        LogStackTrace localCallSite = new LogStackTrace();
         final int policyMask = getThreadPolicyMask();
         final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
 
         final int size = p.readInt();
         for (int i = 0; i < size; i++) {
             final ViolationInfo info = new ViolationInfo(p, !currentlyGathering);
-            info.crashInfo.appendStackTrace(ourStack);
+            info.addLocalStack(localCallSite);
             BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
             if (policy instanceof AndroidBlockGuardPolicy) {
                 ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
@@ -2254,7 +2225,7 @@
             // StrictMode not enabled.
             return;
         }
-        ((AndroidBlockGuardPolicy) policy).onUnbufferedIO();
+        policy.onUnbufferedIO();
     }
 
     /** @hide */
@@ -2264,7 +2235,7 @@
             // StrictMode not enabled.
             return;
         }
-        ((AndroidBlockGuardPolicy) policy).onReadFromDisk();
+        policy.onReadFromDisk();
     }
 
     /** @hide */
@@ -2274,12 +2245,11 @@
             // StrictMode not enabled.
             return;
         }
-        ((AndroidBlockGuardPolicy) policy).onWriteToDisk();
+        policy.onWriteToDisk();
     }
 
-    // Guarded by StrictMode.class
-    private static final HashMap<Class, Integer> sExpectedActivityInstanceCount =
-            new HashMap<Class, Integer>();
+    @GuardedBy("StrictMode.class")
+    private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = new HashMap<>();
 
     /**
      * Returns an object that is used to track instances of activites. The activity should store a
@@ -2354,7 +2324,7 @@
         long instances = VMDebug.countInstancesOfClass(klass, false);
         if (instances > limit) {
             Throwable tr = new InstanceCountViolation(klass, instances, limit);
-            onVmPolicyViolation(tr.getMessage(), tr);
+            onVmPolicyViolation(tr);
         }
     }
 
@@ -2366,10 +2336,15 @@
      */
     @TestApi
     public static final class ViolationInfo implements Parcelable {
-        public final String message;
+        /** Stack and violation details. */
+        @Nullable private final Throwable mThrowable;
 
-        /** Stack and other stuff info. */
-        @Nullable public final ApplicationErrorReport.CrashInfo crashInfo;
+        private final Deque<Throwable> 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;
@@ -2404,19 +2379,13 @@
 
         /** Create an uninitialized instance of ViolationInfo */
         public ViolationInfo() {
-            message = null;
-            crashInfo = null;
+            mThrowable = null;
             policy = 0;
         }
 
-        public ViolationInfo(Throwable tr, int policy) {
-            this(null, tr, policy);
-        }
-
         /** Create an instance of ViolationInfo initialized from an exception. */
-        public ViolationInfo(String message, Throwable tr, int policy) {
-            this.message = message;
-            crashInfo = new ApplicationErrorReport.CrashInfo(tr);
+        public ViolationInfo(Throwable tr, int policy) {
+            this.mThrowable = tr;
             violationUptimeMillis = SystemClock.uptimeMillis();
             this.policy = policy;
             this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
@@ -2446,11 +2415,91 @@
             }
         }
 
+        /** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */
+        public String getStackTrace() {
+            if (mThrowable != null && mStackTrace == null) {
+                StringWriter sw = new StringWriter();
+                PrintWriter pw = new FastPrintWriter(sw, false, 256);
+                mThrowable.printStackTrace(pw);
+                for (Throwable t : mBinderStack) {
+                    pw.append("# via Binder call with stack:\n");
+                    t.printStackTrace(pw);
+                }
+                pw.flush();
+                pw.close();
+                mStackTrace = sw.toString();
+            }
+            return mStackTrace;
+        }
+
+        /**
+         * Optional message describing this violation.
+         *
+         * @hide
+         */
+        @TestApi
+        public String getViolationDetails() {
+            if (mThrowable != null) {
+                return mThrowable.getMessage();
+            } else {
+                return "";
+            }
+        }
+
+        /**
+         * If this violation has a useful stack trace.
+         *
+         * @hide
+         */
+        public boolean hasStackTrace() {
+            return mThrowable != null;
+        }
+
+        /**
+         * Add a {@link Throwable} from the current process that caused the underlying violation.
+         *
+         * @hide
+         */
+        void addLocalStack(Throwable t) {
+            mBinderStack.addFirst(t);
+        }
+
+        /**
+         * Retrieve the type of StrictMode violation.
+         *
+         * @hide
+         */
+        int getViolationBit() {
+            if (mThrowable == null || mThrowable.getMessage() == null) {
+                return 0;
+            }
+            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;
+            }
+        }
+
         @Override
         public int hashCode() {
             int result = 17;
-            if (crashInfo != null) {
-                result = 37 * result + crashInfo.stackTrace.hashCode();
+            if (mThrowable != null) {
+                result = 37 * result + mThrowable.hashCode();
             }
             if (numAnimationsRunning != 0) {
                 result *= 37;
@@ -2478,11 +2527,10 @@
          *     should be removed.
          */
         public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
-            message = in.readString();
-            if (in.readInt() != 0) {
-                crashInfo = new ApplicationErrorReport.CrashInfo(in);
-            } else {
-                crashInfo = null;
+            mThrowable = (Throwable) in.readSerializable();
+            int binderStackSize = in.readInt();
+            for (int i = 0; i < binderStackSize; i++) {
+                mBinderStack.add((Throwable) in.readSerializable());
             }
             int rawPolicy = in.readInt();
             if (unsetGatheringBit) {
@@ -2502,12 +2550,10 @@
         /** Save a ViolationInfo instance to a parcel. */
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeString(message);
-            if (crashInfo != null) {
-                dest.writeInt(1);
-                crashInfo.writeToParcel(dest, flags);
-            } else {
-                dest.writeInt(0);
+            dest.writeSerializable(mThrowable);
+            dest.writeInt(mBinderStack.size());
+            for (Throwable t : mBinderStack) {
+                dest.writeSerializable(t);
             }
             int start = dest.dataPosition();
             dest.writeInt(policy);
@@ -2542,8 +2588,8 @@
 
         /** Dump a ViolationInfo instance to a Printer. */
         public void dump(Printer pw, String prefix) {
-            if (crashInfo != null) {
-                crashInfo.dump(pw, prefix);
+            if (mThrowable != null) {
+                pw.println(prefix + "stackTrace: " + getStackTrace());
             }
             pw.println(prefix + "policy: " + policy);
             if (durationMillis != -1) {
diff --git a/android/os/UserManager.java b/android/os/UserManager.java
index 8c68871..c54b72d 100644
--- a/android/os/UserManager.java
+++ b/android/os/UserManager.java
@@ -319,6 +319,23 @@
     public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
 
     /**
+     * Specifies if date, time and timezone configuring is disallowed.
+     *
+     * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
+     * time and timezone setting on the entire device and all users will be affected. When it's set
+     * by profile owners, it's only applied to the managed user.
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+
+    /**
      * Specifies if a user is disallowed from configuring Tethering
      * & portable hotspots. This can only be set by device owners and profile owners on the
      * primary user. The default value is <code>false</code>.
diff --git a/android/provider/Settings.java b/android/provider/Settings.java
index a27df3a..62f4bf5 100644
--- a/android/provider/Settings.java
+++ b/android/provider/Settings.java
@@ -3096,6 +3096,12 @@
         private static final Validator DIM_SCREEN_VALIDATOR = sBooleanValidator;
 
         /**
+         * The display color mode.
+         * @hide
+         */
+        public static final String DISPLAY_COLOR_MODE = "display_color_mode";
+
+        /**
          * The amount of time in milliseconds before the device goes to sleep or begins
          * to dream after a period of inactivity.  This value is also known as the
          * user activity timeout period since the screen isn't necessarily turned off
diff --git a/android/security/NetworkSecurityPolicy.java b/android/security/NetworkSecurityPolicy.java
index 812c956..0c4eeda 100644
--- a/android/security/NetworkSecurityPolicy.java
+++ b/android/security/NetworkSecurityPolicy.java
@@ -16,7 +16,6 @@
 
 package android.security;
 
-import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.security.net.config.ApplicationConfig;
@@ -63,7 +62,8 @@
      * traffic from applications is handled by higher-level network stacks/components which can
      * honor this aspect of the policy.
      *
-     * <p>NOTE: {@link android.webkit.WebView} does not honor this flag.
+     * <p>NOTE: {@link android.webkit.WebView} honors this flag for applications targeting API level
+     * 26 and up.
      */
     public boolean isCleartextTrafficPermitted() {
         return libcore.net.NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
diff --git a/android/security/net/config/NetworkSecurityConfig.java b/android/security/net/config/NetworkSecurityConfig.java
index b9e5505..52f48ef 100644
--- a/android/security/net/config/NetworkSecurityConfig.java
+++ b/android/security/net/config/NetworkSecurityConfig.java
@@ -164,7 +164,8 @@
      * <p>
      * The default configuration has the following properties:
      * <ol>
-     * <li>Cleartext traffic is permitted for non-ephemeral apps.</li>
+     * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic
+     * is allowed by default.</li>
      * <li>Cleartext traffic is not permitted for ephemeral apps.</li>
      * <li>HSTS is not enforced.</li>
      * <li>No certificate pinning is used.</li>
@@ -183,7 +184,8 @@
                 // System certificate store, does not bypass static pins.
                 .addCertificatesEntryRef(
                         new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
-        final boolean cleartextTrafficPermitted = info.targetSandboxVersion < 2;
+        final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
+                && info.targetSandboxVersion < 2;
         builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
         // Applications targeting N and above must opt in into trusting the user added certificate
         // store.
diff --git a/android/service/autofill/CustomDescription.java b/android/service/autofill/CustomDescription.java
index fd30857..b8e8b19 100644
--- a/android/service/autofill/CustomDescription.java
+++ b/android/service/autofill/CustomDescription.java
@@ -32,7 +32,7 @@
 import java.util.ArrayList;
 
 /**
- * Defines a custom description for the Save UI affordance.
+ * Defines a custom description for the autofill save UI.
  *
  * <p>This is useful when the autofill service needs to show a detailed view of what would be saved;
  * for example, when the screen contains a credit card, it could display a logo of the credit card
@@ -131,7 +131,7 @@
          * <p><b>Note:</b> If any child view of presentation triggers a
          * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent) pending intent
          * on click}, such {@link PendingIntent} must follow the restrictions below, otherwise
-         * it might not be triggered or the Save affordance might not be shown when its activity
+         * it might not be triggered or the autofill save UI might not be shown when its activity
          * is finished:
          * <ul>
          *   <li>It cannot be created with the {@link PendingIntent#FLAG_IMMUTABLE} flag.
diff --git a/android/service/autofill/FillResponse.java b/android/service/autofill/FillResponse.java
index d2033fa..2f6342a 100644
--- a/android/service/autofill/FillResponse.java
+++ b/android/service/autofill/FillResponse.java
@@ -31,6 +31,8 @@
 import android.view.autofill.AutofillId;
 import android.widget.RemoteViews;
 
+import com.android.internal.util.Preconditions;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -51,9 +53,16 @@
      */
     public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
 
+    /**
+     * Used in conjunction to {@link FillResponse.Builder#disableAutofill(long)} to disable autofill
+     * only for the activiy associated with the {@link FillResponse}, instead of the whole app.
+     */
+    public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
+
     /** @hide */
     @IntDef(flag = true, value = {
-            FLAG_TRACK_CONTEXT_COMMITED
+            FLAG_TRACK_CONTEXT_COMMITED,
+            FLAG_DISABLE_ACTIVITY_ONLY
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface FillResponseFlags {}
@@ -65,6 +74,7 @@
     private final @Nullable IntentSender mAuthentication;
     private final @Nullable AutofillId[] mAuthenticationIds;
     private final @Nullable AutofillId[] mIgnoredIds;
+    private final long mDisableDuration;
     private final int mFlags;
     private int mRequestId;
 
@@ -76,6 +86,7 @@
         mAuthentication = builder.mAuthentication;
         mAuthenticationIds = builder.mAuthenticationIds;
         mIgnoredIds = builder.mIgnoredIds;
+        mDisableDuration = builder.mDisableDuration;
         mFlags = builder.mFlags;
         mRequestId = INVALID_REQUEST_ID;
     }
@@ -116,6 +127,11 @@
     }
 
     /** @hide */
+    public long getDisableDuration() {
+        return mDisableDuration;
+    }
+
+    /** @hide */
     public int getFlags() {
         return mFlags;
     }
@@ -150,6 +166,7 @@
         private IntentSender mAuthentication;
         private AutofillId[] mAuthenticationIds;
         private AutofillId[] mIgnoredIds;
+        private long mDisableDuration;
         private int mFlags;
         private boolean mDestroyed;
 
@@ -187,23 +204,25 @@
          * which is used to visualize visualize the response for triggering the authentication
          * flow.
          *
-         * <p></><strong>Note:</strong> Do not make the provided pending intent
+         * <p><b>Note:</b> Do not make the provided pending intent
          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
          * platform needs to fill in the authentication arguments.
          *
          * @param authentication Intent to an activity with your authentication flow.
          * @param presentation The presentation to visualize the response.
-         * @param ids id of Views that when focused will display the authentication UI affordance.
+         * @param ids id of Views that when focused will display the authentication UI.
          *
          * @return This builder.
          * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
-         * neither {@code authentication} nor {@code presentation} is non-{@code null}.
+         * both {@code authentication} and {@code presentation} are {@code null}, or if
+         * both {@code authentication} and {@code presentation} are non-{@code null}
          *
          * @see android.app.PendingIntent#getIntentSender()
          */
         public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
                 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
             throwIfDestroyed();
+            throwIfDisableAutofillCalled();
             if (ids == null || ids.length == 0) {
                 throw new IllegalArgumentException("ids cannot be null or empry");
             }
@@ -226,6 +245,7 @@
          * text field representing the result of a Captcha challenge.
          */
         public Builder setIgnoredIds(AutofillId...ids) {
+            throwIfDestroyed();
             mIgnoredIds = ids;
             return this;
         }
@@ -246,6 +266,7 @@
          */
         public @NonNull Builder addDataset(@Nullable Dataset dataset) {
             throwIfDestroyed();
+            throwIfDisableAutofillCalled();
             if (dataset == null) {
                 return this;
             }
@@ -265,6 +286,7 @@
          */
         public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
             throwIfDestroyed();
+            throwIfDisableAutofillCalled();
             mSaveInfo = saveInfo;
             return this;
         }
@@ -295,30 +317,82 @@
         /**
          * Sets flags changing the response behavior.
          *
-         * @param flags {@link #FLAG_TRACK_CONTEXT_COMMITED}, or {@code 0}.
+         * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and
+         * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}.
          *
          * @return This builder.
          */
         public Builder setFlags(@FillResponseFlags int flags) {
             throwIfDestroyed();
-            mFlags = flags;
+            mFlags = Preconditions.checkFlagsArgument(flags,
+                    FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+            return this;
+        }
+
+        /**
+         * Disables autofill for the app or activity.
+         *
+         * <p>This method is useful to optimize performance in cases where the service knows it
+         * can not autofill an app&mdash;for example, when the service has a list of "blacklisted"
+         * apps such as office suites.
+         *
+         * <p>By default, it disables autofill for all activities in the app, unless the response is
+         * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}.
+         *
+         * <p>Autofill for the app or activity is automatically re-enabled after any of the
+         * following conditions:
+         *
+         * <ol>
+         *   <li>{@code duration} milliseconds have passed.
+         *   <li>The autofill service for the user has changed.
+         *   <li>The device has rebooted.
+         * </ol>
+         *
+         * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain
+         * disabled for autofill until they finish and restart.
+         *
+         * @param duration duration to disable autofill, in milliseconds.
+         *
+         * @return this builder
+         *
+         * @throws IllegalArgumentException if {@code duration} is not a positive number.
+         * @throws IllegalStateException if either {@link #addDataset(Dataset)},
+         *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, or
+         *       {@link #setSaveInfo(SaveInfo)} was already called.
+         */
+        public Builder disableAutofill(long duration) {
+            throwIfDestroyed();
+            if (duration <= 0) {
+                throw new IllegalArgumentException("duration must be greater than 0");
+            }
+            if (mAuthentication != null || mDatasets != null || mSaveInfo != null) {
+                throw new IllegalStateException("disableAutofill() must be the only method called");
+            }
+
+            mDisableDuration = duration;
             return this;
         }
 
         /**
          * Builds a new {@link FillResponse} instance.
          *
-         * <p>You must provide at least one dataset or some savable ids or an authentication with a
-         * presentation view.
+         * @throws IllegalStateException if any of the following conditions occur:
+         * <ol>
+         *   <li>{@link #build()} was already called.
+         *   <li>No call was made to {@link #addDataset(Dataset)},
+         *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+         *       {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}.
+         * </ol>
          *
          * @return A built response.
          */
         public FillResponse build() {
             throwIfDestroyed();
 
-            if (mAuthentication == null && mDatasets == null && mSaveInfo == null) {
-                throw new IllegalArgumentException("need to provide at least one DataSet or a "
-                        + "SaveInfo or an authentication with a presentation");
+            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");
             }
             mDestroyed = true;
             return new FillResponse(this);
@@ -329,6 +403,12 @@
                 throw new IllegalStateException("Already called #build()");
             }
         }
+
+        private void throwIfDisableAutofillCalled() {
+            if (mDisableDuration > 0) {
+                throw new IllegalStateException("Already called #disableAutofill()");
+            }
+        }
     }
 
     /////////////////////////////////////
@@ -348,6 +428,7 @@
                 .append(", hasAuthentication=").append(mAuthentication != null)
                 .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
                 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
+                .append(", disableDuration=").append(mDisableDuration)
                 .append(", flags=").append(mFlags)
                 .append("]")
                 .toString();
@@ -371,6 +452,7 @@
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeParcelable(mPresentation, flags);
         parcel.writeParcelableArray(mIgnoredIds, flags);
+        parcel.writeLong(mDisableDuration);
         parcel.writeInt(mFlags);
         parcel.writeInt(mRequestId);
     }
@@ -402,6 +484,10 @@
             }
 
             builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
+            final long disableDuration = parcel.readLong();
+            if (disableDuration > 0) {
+                builder.disableAutofill(disableDuration);
+            }
             builder.setFlags(parcel.readInt());
 
             final FillResponse response = builder.build();
diff --git a/android/service/autofill/SaveInfo.java b/android/service/autofill/SaveInfo.java
index fde2416..9a1dcbb 100644
--- a/android/service/autofill/SaveInfo.java
+++ b/android/service/autofill/SaveInfo.java
@@ -535,14 +535,15 @@
          * 16 digits, or 15 digits starting with 108:
          *
          * <pre class="prettyprint">
-         * import android.service.autofill.Validators;
+         * import static android.service.autofill.Validators.and;
+         * import static android.service.autofill.Validators.or;
          *
          * Validator validator =
          *   and(
          *     new LuhnChecksumValidator(ccNumberId),
          *     or(
-         *       new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$")),
-         *       new RegexValidator(ccNumberId, Pattern.compile(""^108\\d{12}$"))
+         *       new RegexValidator(ccNumberId, Pattern.compile("^\\d{16}$")),
+         *       new RegexValidator(ccNumberId, Pattern.compile("^108\\d{12}$"))
          *     )
          *   );
          * </pre>
@@ -562,14 +563,14 @@
          * 4 digits on each field:
          *
          * <pre class="prettyprint">
-         * import android.service.autofill.Validators;
+         * import static android.service.autofill.Validators.and;
          *
          * Validator validator =
          *   and(
-         *     new RegexValidator(ccNumberId1, Pattern.compile(""^\\d{4}$")),
-         *     new RegexValidator(ccNumberId2, Pattern.compile(""^\\d{4}$")),
-         *     new RegexValidator(ccNumberId3, Pattern.compile(""^\\d{4}$")),
-         *     new RegexValidator(ccNumberId4, Pattern.compile(""^\\d{4}$"))
+         *     new RegexValidator(ccNumberId1, Pattern.compile("^\\d{4}$")),
+         *     new RegexValidator(ccNumberId2, Pattern.compile("^\\d{4}$")),
+         *     new RegexValidator(ccNumberId3, Pattern.compile("^\\d{4}$")),
+         *     new RegexValidator(ccNumberId4, Pattern.compile("^\\d{4}$"))
          *   );
          * </pre>
          *
diff --git a/android/service/autofill/Transformation.java b/android/service/autofill/Transformation.java
index 4cef261..aa8bc9b 100644
--- a/android/service/autofill/Transformation.java
+++ b/android/service/autofill/Transformation.java
@@ -19,7 +19,7 @@
  * Helper class used to change a child view of a {@link android.widget.RemoteViews presentation
  * template} at runtime, using the values of fields contained in the screen.
  *
- * <p>Typically used by {@link CustomDescription} to provide a customized Save UI affordance.
+ * <p>Typically used by {@link CustomDescription} to provide a customized autofill save UI.
  */
 public interface Transformation {
 }
diff --git a/android/support/car/widget/CarRecyclerView.java b/android/support/car/widget/CarRecyclerView.java
index edc3241..2684c58 100644
--- a/android/support/car/widget/CarRecyclerView.java
+++ b/android/support/car/widget/CarRecyclerView.java
@@ -18,8 +18,6 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.support.annotation.NonNull;
 import android.support.v7.widget.RecyclerView;
 import android.util.AttributeSet;
@@ -27,9 +25,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-
 /**
  * Custom {@link RecyclerView} that helps {@link CarLayoutManager} properly fling and paginate.
  *
@@ -37,11 +32,7 @@
  * #setFadeLastItem(boolean)}.
  */
 public class CarRecyclerView extends RecyclerView {
-    private static final String PARCEL_CLASS = "android.os.Parcel";
-    private static final String SAVED_STATE_CLASS =
-            "android.support.v7.widget.RecyclerView.SavedState";
     private boolean mFadeLastItem;
-    private Constructor<?> mSavedStateConstructor;
     /**
      * If the user releases the list with a velocity of 0, {@link #fling(int, int)} will not be
      * called. However, we want to make sure that the list still snaps to the next page when this
@@ -64,30 +55,6 @@
     }
 
     @Override
-    protected void onRestoreInstanceState(Parcelable state) {
-        if (state.getClass().getClassLoader() != getClass().getClassLoader()) {
-            if (mSavedStateConstructor == null) {
-                mSavedStateConstructor = getSavedStateConstructor();
-            }
-            // Class loader mismatch, recreate from parcel.
-            Parcel obtain = Parcel.obtain();
-            state.writeToParcel(obtain, 0);
-            try {
-                Parcelable newState = (Parcelable) mSavedStateConstructor.newInstance(obtain);
-                super.onRestoreInstanceState(newState);
-            } catch (InstantiationException
-                    | IllegalAccessException
-                    | IllegalArgumentException
-                    | InvocationTargetException e) {
-                // Fail loudy here.
-                throw new RuntimeException(e);
-            }
-        } else {
-            super.onRestoreInstanceState(state);
-        }
-    }
-
-    @Override
     public boolean fling(int velocityX, int velocityY) {
         mWasFlingCalledForGesture = true;
         return ((CarLayoutManager) getLayoutManager()).settleScrollForFling(this, velocityY);
@@ -158,35 +125,6 @@
         smoothScrollToPosition(pageDownPosition);
     }
 
-    /** Sets {@link #mSavedStateConstructor} to private SavedState constructor. */
-    private Constructor<?> getSavedStateConstructor() {
-        Class<?> savedStateClass = null;
-        // Find package private subclass RecyclerView$SavedState.
-        for (Class<?> c : RecyclerView.class.getDeclaredClasses()) {
-            if (c.getCanonicalName().equals(SAVED_STATE_CLASS)) {
-                savedStateClass = c;
-                break;
-            }
-        }
-        if (savedStateClass == null) {
-            throw new RuntimeException("RecyclerView$SavedState not found!");
-        }
-        // Find constructor that takes a {@link Parcel}.
-        for (Constructor<?> c : savedStateClass.getDeclaredConstructors()) {
-            Class<?>[] parameterTypes = c.getParameterTypes();
-            if (parameterTypes.length == 1
-                    && parameterTypes[0].getCanonicalName().equals(PARCEL_CLASS)) {
-                mSavedStateConstructor = c;
-                mSavedStateConstructor.setAccessible(true);
-                break;
-            }
-        }
-        if (mSavedStateConstructor == null) {
-            throw new RuntimeException("RecyclerView$SavedState constructor not found!");
-        }
-        return mSavedStateConstructor;
-    }
-
     /**
      * Fades child by alpha. If child is a {@link ViewGroup} then it will recursively fade its
      * children instead.
diff --git a/android/support/design/widget/CoordinatorLayout.java b/android/support/design/widget/CoordinatorLayout.java
index 94de9b8..477a8d6 100644
--- a/android/support/design/widget/CoordinatorLayout.java
+++ b/android/support/design/widget/CoordinatorLayout.java
@@ -610,8 +610,8 @@
             }
             Constructor<Behavior> c = constructors.get(fullName);
             if (c == null) {
-                final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true,
-                        context.getClassLoader());
+                final Class<Behavior> clazz = (Class<Behavior>) context.getClassLoader()
+                        .loadClass(fullName);
                 c = clazz.getConstructor(CONSTRUCTOR_PARAMS);
                 c.setAccessible(true);
                 constructors.put(fullName, c);
diff --git a/android/support/mediacompat/testlib/IntentConstants.java b/android/support/mediacompat/testlib/IntentConstants.java
index b0e3d5f..a18bcf3 100644
--- a/android/support/mediacompat/testlib/IntentConstants.java
+++ b/android/support/mediacompat/testlib/IntentConstants.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.
diff --git a/android/support/mediacompat/testlib/MediaBrowserConstants.java b/android/support/mediacompat/testlib/MediaBrowserConstants.java
index 8ef0a35..86024d9 100644
--- a/android/support/mediacompat/testlib/MediaBrowserConstants.java
+++ b/android/support/mediacompat/testlib/MediaBrowserConstants.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.
diff --git a/android/support/mediacompat/testlib/MediaControllerConstants.java b/android/support/mediacompat/testlib/MediaControllerConstants.java
index 1de00ef..5fa086b 100644
--- a/android/support/mediacompat/testlib/MediaControllerConstants.java
+++ b/android/support/mediacompat/testlib/MediaControllerConstants.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.
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
index 79381e5..95be162 100644
--- a/android/support/mediacompat/testlib/MediaSessionConstants.java
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.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.
diff --git a/android/support/v17/leanback/app/BrowseFragment.java b/android/support/v17/leanback/app/BrowseFragment.java
index f377389..ae31c4f 100644
--- a/android/support/v17/leanback/app/BrowseFragment.java
+++ b/android/support/v17/leanback/app/BrowseFragment.java
@@ -682,7 +682,6 @@
 
     private ObjectAdapter mAdapter;
     private PresenterSelector mAdapterPresenter;
-    private PresenterSelector mWrappingPresenterSelector;
 
     private int mHeadersState = HEADERS_ENABLED;
     private int mBrandColor = Color.TRANSPARENT;
@@ -767,7 +766,11 @@
      * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
      * DividerRow and PageRow.
      */
-    private void createAndSetWrapperPresenter() {
+    private void updateWrapperPresenter() {
+        if (mAdapter == null) {
+            mAdapterPresenter = null;
+            return;
+        }
         final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
         if (adapterPresenter == null) {
             throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
@@ -812,18 +815,16 @@
      */
     public void setAdapter(ObjectAdapter adapter) {
         mAdapter = adapter;
-        createAndSetWrapperPresenter();
+        updateWrapperPresenter();
         if (getView() == null) {
             return;
         }
-        replaceMainFragment(mSelectedPosition);
 
-        if (adapter != null) {
-            if (mMainFragmentRowsAdapter != null) {
-                mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(adapter));
-            }
-            mHeadersFragment.setAdapter(adapter);
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setAdapter(
+                    adapter == null ? null : new ListRowDataAdapter(adapter));
         }
+        mHeadersFragment.setAdapter(adapter);
     }
 
     public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
diff --git a/android/support/v17/leanback/app/BrowseSupportFragment.java b/android/support/v17/leanback/app/BrowseSupportFragment.java
index 03b3c8a..4a2502a 100644
--- a/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -679,7 +679,6 @@
 
     private ObjectAdapter mAdapter;
     private PresenterSelector mAdapterPresenter;
-    private PresenterSelector mWrappingPresenterSelector;
 
     private int mHeadersState = HEADERS_ENABLED;
     private int mBrandColor = Color.TRANSPARENT;
@@ -764,7 +763,11 @@
      * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
      * DividerRow and PageRow.
      */
-    private void createAndSetWrapperPresenter() {
+    private void updateWrapperPresenter() {
+        if (mAdapter == null) {
+            mAdapterPresenter = null;
+            return;
+        }
         final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
         if (adapterPresenter == null) {
             throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
@@ -809,18 +812,16 @@
      */
     public void setAdapter(ObjectAdapter adapter) {
         mAdapter = adapter;
-        createAndSetWrapperPresenter();
+        updateWrapperPresenter();
         if (getView() == null) {
             return;
         }
-        replaceMainFragment(mSelectedPosition);
 
-        if (adapter != null) {
-            if (mMainFragmentRowsAdapter != null) {
-                mMainFragmentRowsAdapter.setAdapter(new ListRowDataAdapter(adapter));
-            }
-            mHeadersSupportFragment.setAdapter(adapter);
+        if (mMainFragmentRowsAdapter != null) {
+            mMainFragmentRowsAdapter.setAdapter(
+                    adapter == null ? null : new ListRowDataAdapter(adapter));
         }
+        mHeadersSupportFragment.setAdapter(adapter);
     }
 
     public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index af37f77..dded071 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2693,6 +2693,40 @@
         }
     }
 
+    // Observer is registered on Adapter to invalidate saved instance state
+    final RecyclerView.AdapterDataObserver mObServer = new RecyclerView.AdapterDataObserver() {
+        @Override
+        public void onChanged() {
+            mChildrenStates.clear();
+        }
+
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            if (DEBUG) {
+                Log.v(getTag(), "onItemRangeChanged positionStart "
+                        + positionStart + " itemCount " + itemCount);
+            }
+            for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
+                mChildrenStates.remove(i);
+            }
+        }
+
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            mChildrenStates.clear();
+        }
+
+        @Override
+        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+            mChildrenStates.clear();
+        }
+
+        @Override
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            mChildrenStates.clear();
+        }
+    };
+
     @Override
     public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
         if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
@@ -2704,14 +2738,12 @@
                 mFocusPositionOffset += itemCount;
             }
         }
-        mChildrenStates.clear();
     }
 
     @Override
     public void onItemsChanged(RecyclerView recyclerView) {
         if (DEBUG) Log.v(getTag(), "onItemsChanged");
         mFocusPositionOffset = 0;
-        mChildrenStates.clear();
     }
 
     @Override
@@ -2732,7 +2764,6 @@
                 }
             }
         }
-        mChildrenStates.clear();
     }
 
     @Override
@@ -2753,16 +2784,6 @@
                 mFocusPositionOffset += itemCount;
             }
         }
-        mChildrenStates.clear();
-    }
-
-    @Override
-    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
-        if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
-                + positionStart + " itemCount " + itemCount);
-        for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
-            mChildrenStates.remove(i);
-        }
     }
 
     @Override
@@ -3460,12 +3481,16 @@
             mFocusPosition = NO_POSITION;
             mFocusPositionOffset = 0;
             mChildrenStates.clear();
+            oldAdapter.unregisterAdapterDataObserver(mObServer);
         }
         if (newAdapter instanceof FacetProviderAdapter) {
             mFacetProviderAdapter = (FacetProviderAdapter) newAdapter;
         } else {
             mFacetProviderAdapter = null;
         }
+        if (newAdapter != null) {
+            newAdapter.registerAdapterDataObserver(mObServer);
+        }
         super.onAdapterChanged(oldAdapter, newAdapter);
     }
 
diff --git a/android/support/v4/app/FragmentActivity.java b/android/support/v4/app/FragmentActivity.java
index cb3c59a..614ff35 100644
--- a/android/support/v4/app/FragmentActivity.java
+++ b/android/support/v4/app/FragmentActivity.java
@@ -977,7 +977,11 @@
                 continue;
             }
             fragment.mLifecycleRegistry.markState(state);
-            markState(fragment.getChildFragmentManager(), state);
+
+            FragmentManager childFragmentManager = fragment.peekChildFragmentManager();
+            if (childFragmentManager != null) {
+                markState(childFragmentManager, state);
+            }
         }
     }
 }
diff --git a/android/support/v7/widget/TooltipPopup.java b/android/support/v7/widget/TooltipPopup.java
index f707c8f..dc20aa1 100644
--- a/android/support/v7/widget/TooltipPopup.java
+++ b/android/support/v7/widget/TooltipPopup.java
@@ -56,7 +56,7 @@
     TooltipPopup(Context context) {
         mContext = context;
 
-        mContentView = LayoutInflater.from(mContext).inflate(R.layout.tooltip, null);
+        mContentView = LayoutInflater.from(mContext).inflate(R.layout.abc_tooltip, null);
         mMessageView = (TextView) mContentView.findViewById(R.id.message);
 
         mLayoutParams.setTitle(getClass().getSimpleName());
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java
index 99f8cfb..6fc7d23 100644
--- a/android/telephony/CarrierConfigManager.java
+++ b/android/telephony/CarrierConfigManager.java
@@ -210,6 +210,12 @@
     public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
 
     /**
+     * Determine whether user can edit voicemail number in Settings.
+     */
+    public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL =
+            "editable_voicemail_number_setting_bool";
+
+    /**
      * Since the default voicemail number is empty, if a SIM card does not have a voicemail number
      * available the user cannot use voicemail. This flag allows the user to edit the voicemail
      * number in such cases, and is false by default.
@@ -1615,6 +1621,13 @@
     public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL =
             "skip_cf_fail_to_disable_dialog_bool";
 
+    /**
+     * List of the FAC (feature access codes) to dial as a normal call.
+     * @hide
+     */
+    public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
+            "feature_access_codes_string_array";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1674,6 +1687,7 @@
         sDefaults.putBoolean(KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL, true);
         sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false);
+        sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL, true);
         sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false);
         sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false);
         sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false);
@@ -1887,6 +1901,7 @@
         sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
+        sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
     }
 
     /**
diff --git a/android/telephony/CellIdentityCdma.java b/android/telephony/CellIdentityCdma.java
index b39b4c7..ddc938e 100644
--- a/android/telephony/CellIdentityCdma.java
+++ b/android/telephony/CellIdentityCdma.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -50,6 +51,10 @@
      * to +90 degrees).
      */
     private final int mLatitude;
+    // long alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaLong;
+    // short alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaShort;
 
     /**
      * @hide
@@ -60,6 +65,8 @@
         mBasestationId = Integer.MAX_VALUE;
         mLongitude = Integer.MAX_VALUE;
         mLatitude = Integer.MAX_VALUE;
+        mAlphaLong = null;
+        mAlphaShort = null;
     }
 
     /**
@@ -75,19 +82,37 @@
      * @hide
      */
     public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat) {
+        this(nid, sid, bid, lon, lat, null, null);
+    }
+
+    /**
+     * public constructor
+     * @param nid Network Id 0..65535
+     * @param sid CDMA System Id 0..32767
+     * @param bid Base Station Id 0..65535
+     * @param lon Longitude is a decimal number ranges from -2592000
+     *        to 2592000
+     * @param lat Latitude is a decimal number ranges from -1296000
+     *        to 1296000
+     * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+     * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     *
+     * @hide
+     */
+    public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat, String alphal,
+                             String alphas) {
         mNetworkId = nid;
         mSystemId = sid;
         mBasestationId = bid;
         mLongitude = lon;
         mLatitude = lat;
+        mAlphaLong = alphal;
+        mAlphaShort = alphas;
     }
 
     private CellIdentityCdma(CellIdentityCdma cid) {
-        mNetworkId = cid.mNetworkId;
-        mSystemId = cid.mSystemId;
-        mBasestationId = cid.mBasestationId;
-        mLongitude = cid.mLongitude;
-        mLatitude = cid.mLatitude;
+        this(cid.mNetworkId, cid.mSystemId, cid.mBasestationId, cid.mLongitude, cid.mLatitude,
+                cid.mAlphaLong, cid.mAlphaShort);
     }
 
     CellIdentityCdma copy() {
@@ -137,9 +162,26 @@
         return mLatitude;
     }
 
+    /**
+     * @return The long alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string). May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaLong() {
+        return mAlphaLong;
+    }
+
+    /**
+     * @return The short alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string).  May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaShort() {
+        return mAlphaShort;
+    }
+
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude);
+        return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude,
+                mAlphaLong, mAlphaShort);
     }
 
     @Override
@@ -153,11 +195,14 @@
         }
 
         CellIdentityCdma o = (CellIdentityCdma) other;
+
         return mNetworkId == o.mNetworkId &&
                 mSystemId == o.mSystemId &&
                 mBasestationId == o.mBasestationId &&
                 mLatitude == o.mLatitude &&
-                mLongitude == o.mLongitude;
+                mLongitude == o.mLongitude &&
+                TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+                TextUtils.equals(mAlphaShort, o.mAlphaShort);
     }
 
     @Override
@@ -168,6 +213,8 @@
         sb.append(" mBasestationId="); sb.append(mBasestationId);
         sb.append(" mLongitude="); sb.append(mLongitude);
         sb.append(" mLatitude="); sb.append(mLatitude);
+        sb.append(" mAlphaLong="); sb.append(mAlphaLong);
+        sb.append(" mAlphaShort="); sb.append(mAlphaShort);
         sb.append("}");
 
         return sb.toString();
@@ -188,15 +235,15 @@
         dest.writeInt(mBasestationId);
         dest.writeInt(mLongitude);
         dest.writeInt(mLatitude);
+        dest.writeString(mAlphaLong);
+        dest.writeString(mAlphaShort);
     }
 
     /** Construct from Parcel, type has already been processed */
     private CellIdentityCdma(Parcel in) {
-        mNetworkId = in.readInt();
-        mSystemId = in.readInt();
-        mBasestationId = in.readInt();
-        mLongitude = in.readInt();
-        mLatitude = in.readInt();
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readInt(),
+                in.readString(), in.readString());
+
         if (DBG) log("CellIdentityCdma(Parcel): " + toString());
     }
 
diff --git a/android/telephony/CellIdentityGsm.java b/android/telephony/CellIdentityGsm.java
index ec008e2..6276626 100644
--- a/android/telephony/CellIdentityGsm.java
+++ b/android/telephony/CellIdentityGsm.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -30,10 +31,6 @@
     private static final String LOG_TAG = "CellIdentityGsm";
     private static final boolean DBG = false;
 
-    // 3-digit Mobile Country Code, 0..999
-    private final int mMcc;
-    // 2 or 3-digit Mobile Network Code, 0..999
-    private final int mMnc;
     // 16-bit Location Area Code, 0..65535
     private final int mLac;
     // 16-bit GSM Cell Identity described in TS 27.007, 0..65535
@@ -42,17 +39,27 @@
     private final int mArfcn;
     // 6-bit Base Station Identity Code
     private final int mBsic;
+    // 3-digit Mobile Country Code in string format
+    private final String mMccStr;
+    // 2 or 3-digit Mobile Network Code in string format
+    private final String mMncStr;
+    // long alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaLong;
+    // short alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaShort;
 
     /**
      * @hide
      */
     public CellIdentityGsm() {
-        mMcc = Integer.MAX_VALUE;
-        mMnc = Integer.MAX_VALUE;
         mLac = Integer.MAX_VALUE;
         mCid = Integer.MAX_VALUE;
         mArfcn = Integer.MAX_VALUE;
         mBsic = Integer.MAX_VALUE;
+        mMccStr = null;
+        mMncStr = null;
+        mAlphaLong = null;
+        mAlphaShort = null;
     }
     /**
      * public constructor
@@ -64,7 +71,8 @@
      * @hide
      */
     public CellIdentityGsm (int mcc, int mnc, int lac, int cid) {
-        this(mcc, mnc, lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE);
+        this(lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE,
+                String.valueOf(mcc), String.valueOf(mnc), null, null);
     }
 
     /**
@@ -79,39 +87,81 @@
      * @hide
      */
     public CellIdentityGsm (int mcc, int mnc, int lac, int cid, int arfcn, int bsic) {
-        mMcc = mcc;
-        mMnc = mnc;
+        this(lac, cid, arfcn, bsic, String.valueOf(mcc), String.valueOf(mnc), null, null);
+    }
+
+    /**
+     * public constructor
+     * @param lac 16-bit Location Area Code, 0..65535
+     * @param cid 16-bit GSM Cell Identity or 28-bit UMTS Cell Identity
+     * @param arfcn 16-bit GSM Absolute RF Channel Number
+     * @param bsic 6-bit Base Station Identity Code
+     * @param mccStr 3-digit Mobile Country Code in string format
+     * @param mncStr 2 or 3-digit Mobile Network Code in string format
+     * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+     * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     *
+     * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+     * not a 2 or 3-digit code.
+     *
+     * @hide
+     */
+    public CellIdentityGsm (int lac, int cid, int arfcn, int bsic, String mccStr,
+                            String mncStr, String alphal, String alphas) {
         mLac = lac;
         mCid = cid;
         mArfcn = arfcn;
-        mBsic = bsic;
+        // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator
+        // for inbound parcels
+        mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic;
+
+        if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+            mMccStr = mccStr;
+        } else if (mccStr.isEmpty()) {
+            // If the mccStr parsed from Parcel is empty, set it as null.
+            mMccStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MCC format");
+        }
+
+        if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+            mMncStr = mncStr;
+        } else if (mncStr.isEmpty()) {
+            // If the mncStr parsed from Parcel is empty, set it as null.
+            mMncStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MNC format");
+        }
+
+        mAlphaLong = alphal;
+        mAlphaShort = alphas;
     }
 
     private CellIdentityGsm(CellIdentityGsm cid) {
-        mMcc = cid.mMcc;
-        mMnc = cid.mMnc;
-        mLac = cid.mLac;
-        mCid = cid.mCid;
-        mArfcn = cid.mArfcn;
-        mBsic = cid.mBsic;
+        this(cid.mLac, cid.mCid, cid.mArfcn, cid.mBsic, cid.mMccStr,
+                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
     }
 
     CellIdentityGsm copy() {
-       return new CellIdentityGsm(this);
+        return new CellIdentityGsm(this);
     }
 
     /**
      * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMccStr} instead.
      */
+    @Deprecated
     public int getMcc() {
-        return mMcc;
+        return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
     }
 
     /**
      * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMncStr} instead.
      */
+    @Deprecated
     public int getMnc() {
-        return mMnc;
+        return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
     }
 
     /**
@@ -144,6 +194,43 @@
         return mBsic;
     }
 
+    /**
+     * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+     */
+    public String getMobileNetworkOperator() {
+        return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+    }
+
+    /**
+     * @return Mobile Country Code in string format, null if unknown
+     */
+    public String getMccStr() {
+        return mMccStr;
+    }
+
+    /**
+     * @return Mobile Network Code in string format, null if unknown
+     */
+    public String getMncStr() {
+        return mMncStr;
+    }
+
+    /**
+     * @return The long alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string). May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaLong() {
+        return mAlphaLong;
+    }
+
+    /**
+     * @return The short alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string).  May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaShort() {
+        return mAlphaShort;
+    }
+
 
     /**
      * @return Integer.MAX_VALUE, undefined for GSM
@@ -155,7 +242,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mMcc, mMnc, mLac, mCid);
+        return Objects.hash(mMccStr, mMncStr, mLac, mCid, mAlphaLong, mAlphaShort);
     }
 
     @Override
@@ -169,23 +256,27 @@
         }
 
         CellIdentityGsm o = (CellIdentityGsm) other;
-        return mMcc == o.mMcc &&
-                mMnc == o.mMnc &&
-                mLac == o.mLac &&
+        return mLac == o.mLac &&
                 mCid == o.mCid &&
                 mArfcn == o.mArfcn &&
-                mBsic == o.mBsic;
+                mBsic == o.mBsic &&
+                TextUtils.equals(mMccStr, o.mMccStr) &&
+                TextUtils.equals(mMncStr, o.mMncStr) &&
+                TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+                TextUtils.equals(mAlphaShort, o.mAlphaShort);
     }
 
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("CellIdentityGsm:{");
-        sb.append(" mMcc=").append(mMcc);
-        sb.append(" mMnc=").append(mMnc);
         sb.append(" mLac=").append(mLac);
         sb.append(" mCid=").append(mCid);
         sb.append(" mArfcn=").append(mArfcn);
         sb.append(" mBsic=").append("0x").append(Integer.toHexString(mBsic));
+        sb.append(" mMcc=").append(mMccStr);
+        sb.append(" mMnc=").append(mMncStr);
+        sb.append(" mAlphaLong=").append(mAlphaLong);
+        sb.append(" mAlphaShort=").append(mAlphaShort);
         sb.append("}");
 
         return sb.toString();
@@ -201,26 +292,20 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         if (DBG) log("writeToParcel(Parcel, int): " + toString());
-        dest.writeInt(mMcc);
-        dest.writeInt(mMnc);
         dest.writeInt(mLac);
         dest.writeInt(mCid);
         dest.writeInt(mArfcn);
         dest.writeInt(mBsic);
+        dest.writeString(mMccStr);
+        dest.writeString(mMncStr);
+        dest.writeString(mAlphaLong);
+        dest.writeString(mAlphaShort);
     }
 
     /** Construct from Parcel, type has already been processed */
     private CellIdentityGsm(Parcel in) {
-        mMcc = in.readInt();
-        mMnc = in.readInt();
-        mLac = in.readInt();
-        mCid = in.readInt();
-        mArfcn = in.readInt();
-        int bsic = in.readInt();
-        // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator
-        // for inbound parcels
-        if (bsic == 0xFF) bsic = Integer.MAX_VALUE;
-        mBsic = bsic;
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+                in.readString(), in.readString(), in.readString());
 
         if (DBG) log("CellIdentityGsm(Parcel): " + toString());
     }
@@ -229,16 +314,16 @@
     @SuppressWarnings("hiding")
     public static final Creator<CellIdentityGsm> CREATOR =
             new Creator<CellIdentityGsm>() {
-        @Override
-        public CellIdentityGsm createFromParcel(Parcel in) {
-            return new CellIdentityGsm(in);
-        }
+                @Override
+                public CellIdentityGsm createFromParcel(Parcel in) {
+                    return new CellIdentityGsm(in);
+                }
 
-        @Override
-        public CellIdentityGsm[] newArray(int size) {
-            return new CellIdentityGsm[size];
-        }
-    };
+                @Override
+                public CellIdentityGsm[] newArray(int size) {
+                    return new CellIdentityGsm[size];
+                }
+            };
 
     /**
      * log
@@ -246,4 +331,4 @@
     private static void log(String s) {
         Rlog.w(LOG_TAG, s);
     }
-}
+}
\ No newline at end of file
diff --git a/android/telephony/CellIdentityLte.java b/android/telephony/CellIdentityLte.java
index ce74383..74d2966 100644
--- a/android/telephony/CellIdentityLte.java
+++ b/android/telephony/CellIdentityLte.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -30,10 +31,6 @@
     private static final String LOG_TAG = "CellIdentityLte";
     private static final boolean DBG = false;
 
-    // 3-digit Mobile Country Code, 0..999
-    private final int mMcc;
-    // 2 or 3-digit Mobile Network Code, 0..999
-    private final int mMnc;
     // 28-bit cell identity
     private final int mCi;
     // physical cell id 0..503
@@ -42,17 +39,27 @@
     private final int mTac;
     // 18-bit Absolute RF Channel Number
     private final int mEarfcn;
+    // 3-digit Mobile Country Code in string format
+    private final String mMccStr;
+    // 2 or 3-digit Mobile Network Code in string format
+    private final String mMncStr;
+    // long alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaLong;
+    // short alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaShort;
 
     /**
      * @hide
      */
     public CellIdentityLte() {
-        mMcc = Integer.MAX_VALUE;
-        mMnc = Integer.MAX_VALUE;
         mCi = Integer.MAX_VALUE;
         mPci = Integer.MAX_VALUE;
         mTac = Integer.MAX_VALUE;
         mEarfcn = Integer.MAX_VALUE;
+        mMccStr = null;
+        mMncStr = null;
+        mAlphaLong = null;
+        mAlphaShort = null;
     }
 
     /**
@@ -66,7 +73,7 @@
      * @hide
      */
     public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac) {
-        this(mcc, mnc, ci, pci, tac, Integer.MAX_VALUE);
+        this(ci, pci, tac, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), null, null);
     }
 
     /**
@@ -81,21 +88,57 @@
      * @hide
      */
     public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac, int earfcn) {
-        mMcc = mcc;
-        mMnc = mnc;
+        this(ci, pci, tac, earfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+    }
+
+    /**
+     *
+     * @param ci 28-bit Cell Identity
+     * @param pci Physical Cell Id 0..503
+     * @param tac 16-bit Tracking Area Code
+     * @param earfcn 18-bit LTE Absolute RF Channel Number
+     * @param mccStr 3-digit Mobile Country Code in string format
+     * @param mncStr 2 or 3-digit Mobile Network Code in string format
+     * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+     * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     *
+     * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+     * not a 2 or 3-digit code.
+     *
+     * @hide
+     */
+    public CellIdentityLte (int ci, int pci, int tac, int earfcn, String mccStr,
+                            String mncStr, String alphal, String alphas) {
         mCi = ci;
         mPci = pci;
         mTac = tac;
         mEarfcn = earfcn;
+
+        if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+            mMccStr = mccStr;
+        } else if (mccStr.isEmpty()) {
+            // If the mccStr parsed from Parcel is empty, set it as null.
+            mMccStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MCC format");
+        }
+
+        if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+            mMncStr = mncStr;
+        } else if (mncStr.isEmpty()) {
+            // If the mncStr parsed from Parcel is empty, set it as null.
+            mMncStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MNC format");
+        }
+
+        mAlphaLong = alphal;
+        mAlphaShort = alphas;
     }
 
     private CellIdentityLte(CellIdentityLte cid) {
-        mMcc = cid.mMcc;
-        mMnc = cid.mMnc;
-        mCi = cid.mCi;
-        mPci = cid.mPci;
-        mTac = cid.mTac;
-        mEarfcn = cid.mEarfcn;
+        this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mMccStr,
+                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
     }
 
     CellIdentityLte copy() {
@@ -104,16 +147,20 @@
 
     /**
      * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMccStr} instead.
      */
+    @Deprecated
     public int getMcc() {
-        return mMcc;
+        return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
     }
 
     /**
      * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMncStr} instead.
      */
+    @Deprecated
     public int getMnc() {
-        return mMnc;
+        return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
     }
 
     /**
@@ -144,9 +191,46 @@
         return mEarfcn;
     }
 
+    /**
+     * @return Mobile Country Code in string format, null if unknown
+     */
+    public String getMccStr() {
+        return mMccStr;
+    }
+
+    /**
+     * @return Mobile Network Code in string format, null if unknown
+     */
+    public String getMncStr() {
+        return mMncStr;
+    }
+
+    /**
+     * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+     */
+    public String getMobileNetworkOperator() {
+        return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+    }
+
+    /**
+     * @return The long alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string). May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaLong() {
+        return mAlphaLong;
+    }
+
+    /**
+     * @return The short alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string).  May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaShort() {
+        return mAlphaShort;
+    }
+
     @Override
     public int hashCode() {
-        return Objects.hash(mMcc, mMnc, mCi, mPci, mTac);
+        return Objects.hash(mMccStr, mMncStr, mCi, mPci, mTac, mAlphaLong, mAlphaShort);
     }
 
     @Override
@@ -160,23 +244,27 @@
         }
 
         CellIdentityLte o = (CellIdentityLte) other;
-        return mMcc == o.mMcc &&
-                mMnc == o.mMnc &&
-                mCi == o.mCi &&
+        return mCi == o.mCi &&
                 mPci == o.mPci &&
                 mTac == o.mTac &&
-                mEarfcn == o.mEarfcn;
+                mEarfcn == o.mEarfcn &&
+                TextUtils.equals(mMccStr, o.mMccStr) &&
+                TextUtils.equals(mMncStr, o.mMncStr) &&
+                TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+                TextUtils.equals(mAlphaShort, o.mAlphaShort);
     }
 
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("CellIdentityLte:{");
-        sb.append(" mMcc="); sb.append(mMcc);
-        sb.append(" mMnc="); sb.append(mMnc);
         sb.append(" mCi="); sb.append(mCi);
         sb.append(" mPci="); sb.append(mPci);
         sb.append(" mTac="); sb.append(mTac);
         sb.append(" mEarfcn="); sb.append(mEarfcn);
+        sb.append(" mMcc="); sb.append(mMccStr);
+        sb.append(" mMnc="); sb.append(mMncStr);
+        sb.append(" mAlphaLong="); sb.append(mAlphaLong);
+        sb.append(" mAlphaShort="); sb.append(mAlphaShort);
         sb.append("}");
 
         return sb.toString();
@@ -192,22 +280,21 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         if (DBG) log("writeToParcel(Parcel, int): " + toString());
-        dest.writeInt(mMcc);
-        dest.writeInt(mMnc);
         dest.writeInt(mCi);
         dest.writeInt(mPci);
         dest.writeInt(mTac);
         dest.writeInt(mEarfcn);
+        dest.writeString(mMccStr);
+        dest.writeString(mMncStr);
+        dest.writeString(mAlphaLong);
+        dest.writeString(mAlphaShort);
     }
 
     /** Construct from Parcel, type has already been processed */
     private CellIdentityLte(Parcel in) {
-        mMcc = in.readInt();
-        mMnc = in.readInt();
-        mCi = in.readInt();
-        mPci = in.readInt();
-        mTac = in.readInt();
-        mEarfcn = in.readInt();
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+                in.readString(), in.readString(), in.readString());
+
         if (DBG) log("CellIdentityLte(Parcel): " + toString());
     }
 
@@ -215,16 +302,16 @@
     @SuppressWarnings("hiding")
     public static final Creator<CellIdentityLte> CREATOR =
             new Creator<CellIdentityLte>() {
-        @Override
-        public CellIdentityLte createFromParcel(Parcel in) {
-            return new CellIdentityLte(in);
-        }
+                @Override
+                public CellIdentityLte createFromParcel(Parcel in) {
+                    return new CellIdentityLte(in);
+                }
 
-        @Override
-        public CellIdentityLte[] newArray(int size) {
-            return new CellIdentityLte[size];
-        }
-    };
+                @Override
+                public CellIdentityLte[] newArray(int size) {
+                    return new CellIdentityLte[size];
+                }
+            };
 
     /**
      * log
@@ -232,4 +319,4 @@
     private static void log(String s) {
         Rlog.w(LOG_TAG, s);
     }
-}
+}
\ No newline at end of file
diff --git a/android/telephony/CellIdentityWcdma.java b/android/telephony/CellIdentityWcdma.java
index 0d13efd..51b11aa 100644
--- a/android/telephony/CellIdentityWcdma.java
+++ b/android/telephony/CellIdentityWcdma.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -30,10 +31,6 @@
     private static final String LOG_TAG = "CellIdentityWcdma";
     private static final boolean DBG = false;
 
-    // 3-digit Mobile Country Code, 0..999
-    private final int mMcc;
-    // 2 or 3-digit Mobile Network Code, 0..999
-    private final int mMnc;
     // 16-bit Location Area Code, 0..65535
     private final int mLac;
     // 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455
@@ -42,17 +39,27 @@
     private final int mPsc;
     // 16-bit UMTS Absolute RF Channel Number
     private final int mUarfcn;
+    // 3-digit Mobile Country Code in string format
+    private final String mMccStr;
+    // 2 or 3-digit Mobile Network Code in string format
+    private final String mMncStr;
+    // long alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaLong;
+    // short alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaShort;
 
     /**
      * @hide
      */
     public CellIdentityWcdma() {
-        mMcc = Integer.MAX_VALUE;
-        mMnc = Integer.MAX_VALUE;
         mLac = Integer.MAX_VALUE;
         mCid = Integer.MAX_VALUE;
         mPsc = Integer.MAX_VALUE;
         mUarfcn = Integer.MAX_VALUE;
+        mMccStr = null;
+        mMncStr = null;
+        mAlphaLong = null;
+        mAlphaShort = null;
     }
     /**
      * public constructor
@@ -65,7 +72,8 @@
      * @hide
      */
     public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) {
-        this(mcc, mnc, lac, cid, psc, Integer.MAX_VALUE);
+        this(lac, cid, psc, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc),
+                null, null);
     }
 
     /**
@@ -80,39 +88,79 @@
      * @hide
      */
     public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc, int uarfcn) {
-        mMcc = mcc;
-        mMnc = mnc;
+        this(lac, cid, psc, uarfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+    }
+
+    /**
+     * public constructor
+     * @param lac 16-bit Location Area Code, 0..65535
+     * @param cid 28-bit UMTS Cell Identity
+     * @param psc 9-bit UMTS Primary Scrambling Code
+     * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+     * @param mccStr 3-digit Mobile Country Code in string format
+     * @param mncStr 2 or 3-digit Mobile Network Code in string format
+     * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+     * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     *
+     * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+     * not a 2 or 3-digit code.
+     *
+     * @hide
+     */
+    public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn,
+                              String mccStr, String mncStr, String alphal, String alphas) {
         mLac = lac;
         mCid = cid;
         mPsc = psc;
         mUarfcn = uarfcn;
+
+        if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+            mMccStr = mccStr;
+        } else if (mccStr.isEmpty()) {
+            // If the mccStr parsed from Parcel is empty, set it as null.
+            mMccStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MCC format");
+        }
+
+        if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+            mMncStr = mncStr;
+        } else if (mncStr.isEmpty()) {
+            // If the mncStr parsed from Parcel is empty, set it as null.
+            mMncStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MNC format");
+        }
+
+        mAlphaLong = alphal;
+        mAlphaShort = alphas;
     }
 
     private CellIdentityWcdma(CellIdentityWcdma cid) {
-        mMcc = cid.mMcc;
-        mMnc = cid.mMnc;
-        mLac = cid.mLac;
-        mCid = cid.mCid;
-        mPsc = cid.mPsc;
-        mUarfcn = cid.mUarfcn;
+        this(cid.mLac, cid.mCid, cid.mPsc, cid.mUarfcn, cid.mMccStr,
+                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
     }
 
     CellIdentityWcdma copy() {
-       return new CellIdentityWcdma(this);
+        return new CellIdentityWcdma(this);
     }
 
     /**
      * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMccStr} instead.
      */
+    @Deprecated
     public int getMcc() {
-        return mMcc;
+        return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
     }
 
     /**
      * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMncStr} instead.
      */
+    @Deprecated
     public int getMnc() {
-        return mMnc;
+        return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
     }
 
     /**
@@ -138,9 +186,46 @@
         return mPsc;
     }
 
+    /**
+     * @return Mobile Country Code in string version, null if unknown
+     */
+    public String getMccStr() {
+        return mMccStr;
+    }
+
+    /**
+     * @return Mobile Network Code in string version, null if unknown
+     */
+    public String getMncStr() {
+        return mMncStr;
+    }
+
+    /**
+     * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+     */
+    public String getMobileNetworkOperator() {
+        return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+    }
+
+    /**
+     * @return The long alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string). May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaLong() {
+        return mAlphaLong;
+    }
+
+    /**
+     * @return The short alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string).  May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaShort() {
+        return mAlphaShort;
+    }
+
     @Override
     public int hashCode() {
-        return Objects.hash(mMcc, mMnc, mLac, mCid, mPsc);
+        return Objects.hash(mMccStr, mMncStr, mLac, mCid, mPsc, mAlphaLong, mAlphaShort);
     }
 
     /**
@@ -161,23 +246,27 @@
         }
 
         CellIdentityWcdma o = (CellIdentityWcdma) other;
-        return mMcc == o.mMcc &&
-                mMnc == o.mMnc &&
-                mLac == o.mLac &&
+        return mLac == o.mLac &&
                 mCid == o.mCid &&
                 mPsc == o.mPsc &&
-                mUarfcn == o.mUarfcn;
+                mUarfcn == o.mUarfcn &&
+                TextUtils.equals(mMccStr, o.mMccStr) &&
+                TextUtils.equals(mMncStr, o.mMncStr) &&
+                TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+                TextUtils.equals(mAlphaShort, o.mAlphaShort);
     }
 
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("CellIdentityWcdma:{");
-        sb.append(" mMcc=").append(mMcc);
-        sb.append(" mMnc=").append(mMnc);
         sb.append(" mLac=").append(mLac);
         sb.append(" mCid=").append(mCid);
         sb.append(" mPsc=").append(mPsc);
         sb.append(" mUarfcn=").append(mUarfcn);
+        sb.append(" mMcc=").append(mMccStr);
+        sb.append(" mMnc=").append(mMncStr);
+        sb.append(" mAlphaLong=").append(mAlphaLong);
+        sb.append(" mAlphaShort=").append(mAlphaShort);
         sb.append("}");
 
         return sb.toString();
@@ -193,22 +282,21 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         if (DBG) log("writeToParcel(Parcel, int): " + toString());
-        dest.writeInt(mMcc);
-        dest.writeInt(mMnc);
         dest.writeInt(mLac);
         dest.writeInt(mCid);
         dest.writeInt(mPsc);
         dest.writeInt(mUarfcn);
+        dest.writeString(mMccStr);
+        dest.writeString(mMncStr);
+        dest.writeString(mAlphaLong);
+        dest.writeString(mAlphaShort);
     }
 
     /** Construct from Parcel, type has already been processed */
     private CellIdentityWcdma(Parcel in) {
-        mMcc = in.readInt();
-        mMnc = in.readInt();
-        mLac = in.readInt();
-        mCid = in.readInt();
-        mPsc = in.readInt();
-        mUarfcn = in.readInt();
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+                in.readString(), in.readString(), in.readString());
+
         if (DBG) log("CellIdentityWcdma(Parcel): " + toString());
     }
 
@@ -216,16 +304,16 @@
     @SuppressWarnings("hiding")
     public static final Creator<CellIdentityWcdma> CREATOR =
             new Creator<CellIdentityWcdma>() {
-        @Override
-        public CellIdentityWcdma createFromParcel(Parcel in) {
-            return new CellIdentityWcdma(in);
-        }
+                @Override
+                public CellIdentityWcdma createFromParcel(Parcel in) {
+                    return new CellIdentityWcdma(in);
+                }
 
-        @Override
-        public CellIdentityWcdma[] newArray(int size) {
-            return new CellIdentityWcdma[size];
-        }
-    };
+                @Override
+                public CellIdentityWcdma[] newArray(int size) {
+                    return new CellIdentityWcdma[size];
+                }
+            };
 
     /**
      * log
@@ -233,4 +321,4 @@
     private static void log(String s) {
         Rlog.w(LOG_TAG, s);
     }
-}
+}
\ No newline at end of file
diff --git a/android/telephony/DisconnectCause.java b/android/telephony/DisconnectCause.java
index 98fb653..c3a2ceb 100644
--- a/android/telephony/DisconnectCause.java
+++ b/android/telephony/DisconnectCause.java
@@ -273,6 +273,13 @@
      * {@hide}
      */
     public static final int EMERGENCY_PERM_FAILURE = 64;
+
+    /**
+     * This cause is used to report a normal event only when no other cause in the normal class
+     * applies.
+     * {@hide}
+     */
+    public static final int NORMAL_UNSPECIFIED = 65;
     //*********************************************************************************************
     // When adding a disconnect type:
     // 1) Update toString() with the newly added disconnect type.
@@ -413,6 +420,8 @@
             return "EMERGENCY_TEMP_FAILURE";
         case EMERGENCY_PERM_FAILURE:
             return "EMERGENCY_PERM_FAILURE";
+        case NORMAL_UNSPECIFIED:
+            return "NORMAL_UNSPECIFIED";
         default:
             return "INVALID: " + cause;
         }
diff --git a/android/telephony/MbmsDownloadSession.java b/android/telephony/MbmsDownloadSession.java
index 9a9877a..f392570 100644
--- a/android/telephony/MbmsDownloadSession.java
+++ b/android/telephony/MbmsDownloadSession.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -74,6 +75,14 @@
             "android.telephony.action.EmbmsDownload";
 
     /**
+     * Metadata key that specifies the component name of the service to bind to for file-download.
+     * @hide
+     */
+    @TestApi
+    public static final String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA =
+            "mbms-download-service-override";
+
+    /**
      * Integer extra that Android will attach to the intent supplied via
      * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
      * Indicates the result code of the download. One of
diff --git a/android/telephony/MbmsStreamingSession.java b/android/telephony/MbmsStreamingSession.java
index a8c4607..fb2ff7b 100644
--- a/android/telephony/MbmsStreamingSession.java
+++ b/android/telephony/MbmsStreamingSession.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
@@ -62,6 +63,14 @@
     public static final String MBMS_STREAMING_SERVICE_ACTION =
             "android.telephony.action.EmbmsStreaming";
 
+    /**
+     * Metadata key that specifies the component name of the service to bind to for file-download.
+     * @hide
+     */
+    @TestApi
+    public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA =
+            "mbms-streaming-service-override";
+
     private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
 
     private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
diff --git a/android/telephony/SmsManager.java b/android/telephony/SmsManager.java
index 6029995..98195ad 100644
--- a/android/telephony/SmsManager.java
+++ b/android/telephony/SmsManager.java
@@ -390,20 +390,23 @@
      * Inject an SMS PDU into the android application framework.
      *
      * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
-     * privileges. @see android.telephony.TelephonyManager#hasCarrierPrivileges
+     * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param pdu is the byte array of pdu to be injected into android application framework
-     * @param format is the format of SMS pdu (3gpp or 3gpp2)
+     * @param format is the format of SMS pdu ({@link SmsMessage#FORMAT_3GPP} or
+     *  {@link SmsMessage#FORMAT_3GPP2})
      * @param receivedIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is successfully received by the
      *  android application framework, or failed. This intent is broadcasted at
      *  the same time an SMS received from radio is acknowledged back.
-     *  The result code will be <code>RESULT_SMS_HANDLED</code> for success, or
-     *  <code>RESULT_SMS_GENERIC_ERROR</code> for error.
+     *  The result code will be {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_HANDLED}
+     *  for success, or {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_GENERIC_ERROR} for
+     *  error.
      *
-     * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2.
+     * @throws IllegalArgumentException if the format is invalid.
      */
-    public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
+    public void injectSmsPdu(
+            byte[] pdu, @SmsMessage.Format String format, PendingIntent receivedIntent) {
         if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
             // Format must be either 3gpp or 3gpp2.
             throw new IllegalArgumentException(
diff --git a/android/telephony/SmsMessage.java b/android/telephony/SmsMessage.java
index dcdda86..df41233 100644
--- a/android/telephony/SmsMessage.java
+++ b/android/telephony/SmsMessage.java
@@ -16,24 +16,25 @@
 
 package android.telephony;
 
-import android.os.Binder;
-import android.os.Parcel;
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+import android.annotation.StringDef;
 import android.content.res.Resources;
+import android.os.Binder;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
 import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
-import com.android.internal.telephony.Sms7BitEncodingTranslator;
 
-import java.lang.Math;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 
-import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
-
 
 /**
  * A Short Message Service message.
@@ -81,15 +82,18 @@
      */
     public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
 
+    /** @hide */
+    @StringDef({FORMAT_3GPP, FORMAT_3GPP2})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Format {}
+
     /**
      * Indicates a 3GPP format SMS message.
-     * @hide pending API council approval
      */
     public static final String FORMAT_3GPP = "3gpp";
 
     /**
      * Indicates a 3GPP2 format SMS message.
-     * @hide pending API council approval
      */
     public static final String FORMAT_3GPP2 = "3gpp2";
 
diff --git a/android/telephony/mbms/MbmsUtils.java b/android/telephony/mbms/MbmsUtils.java
index d38d8a7..b4ad1d7 100644
--- a/android/telephony/mbms/MbmsUtils.java
+++ b/android/telephony/mbms/MbmsUtils.java
@@ -22,6 +22,8 @@
 import android.content.ServiceConnection;
 import android.content.pm.*;
 import android.content.pm.ServiceInfo;
+import android.telephony.MbmsDownloadSession;
+import android.telephony.MbmsStreamingSession;
 import android.util.Log;
 
 import java.io.File;
@@ -48,24 +50,64 @@
         return new ComponentName(ci.packageName, ci.name);
     }
 
+    private static ComponentName getOverrideServiceName(Context context, String serviceAction) {
+        String metaDataKey = null;
+        switch (serviceAction) {
+            case MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION:
+                metaDataKey = MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA;
+                break;
+            case MbmsStreamingSession.MBMS_STREAMING_SERVICE_ACTION:
+                metaDataKey = MbmsStreamingSession.MBMS_STREAMING_SERVICE_OVERRIDE_METADATA;
+                break;
+        }
+        if (metaDataKey == null) {
+            return null;
+        }
+
+        ApplicationInfo appInfo;
+        try {
+            appInfo = context.getPackageManager()
+                    .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+        if (appInfo.metaData == null) {
+            return null;
+        }
+        String serviceComponent = appInfo.metaData.getString(metaDataKey);
+        if (serviceComponent == null) {
+            return null;
+        }
+        return ComponentName.unflattenFromString(serviceComponent);
+    }
+
     public static ServiceInfo getMiddlewareServiceInfo(Context context, String serviceAction) {
         // Query for the proper service
         PackageManager packageManager = context.getPackageManager();
         Intent queryIntent = new Intent();
         queryIntent.setAction(serviceAction);
-        List<ResolveInfo> downloadServices = packageManager.queryIntentServices(queryIntent,
-                PackageManager.MATCH_SYSTEM_ONLY);
 
-        if (downloadServices == null || downloadServices.size() == 0) {
-            Log.w(LOG_TAG, "No download services found, cannot get service info");
+        ComponentName overrideService = getOverrideServiceName(context, serviceAction);
+        List<ResolveInfo> services;
+        if (overrideService == null) {
+            services = packageManager.queryIntentServices(queryIntent,
+                    PackageManager.MATCH_SYSTEM_ONLY);
+        } else {
+            queryIntent.setComponent(overrideService);
+            services = packageManager.queryIntentServices(queryIntent,
+                    PackageManager.MATCH_ALL);
+        }
+
+        if (services == null || services.size() == 0) {
+            Log.w(LOG_TAG, "No MBMS services found, cannot get service info");
             return null;
         }
 
-        if (downloadServices.size() > 1) {
-            Log.w(LOG_TAG, "More than one download service found, cannot get unique service");
+        if (services.size() > 1) {
+            Log.w(LOG_TAG, "More than one MBMS service found, cannot get unique service");
             return null;
         }
-        return downloadServices.get(0).serviceInfo;
+        return services.get(0).serviceInfo;
     }
 
     public static int startBinding(Context context, String serviceAction,
diff --git a/android/telephony/mbms/StreamingServiceInfo.java b/android/telephony/mbms/StreamingServiceInfo.java
index c704f34..ef2a14a 100644
--- a/android/telephony/mbms/StreamingServiceInfo.java
+++ b/android/telephony/mbms/StreamingServiceInfo.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;
 
@@ -41,6 +42,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public StreamingServiceInfo(Map<Locale, String> names, String className,
             List<Locale> locales, String serviceId, Date start, Date end) {
         super(names, className, locales, serviceId, start, end);
diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index a238153..db177c0 100644
--- a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Binder;
@@ -38,6 +39,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
     /**
      * Initialize streaming service for this app and subId, registering the listener.
diff --git a/android/text/Layout.java b/android/text/Layout.java
index ac5c2e9..4d2a962 100644
--- a/android/text/Layout.java
+++ b/android/text/Layout.java
@@ -1910,7 +1910,7 @@
         MeasuredText mt = MeasuredText.obtain();
         TextLine tl = TextLine.obtain();
         try {
-            mt.setPara(text, start, end, textDir, null);
+            mt.setPara(text, start, end, textDir);
             Directions directions;
             int dir;
             if (mt.mEasy) {
diff --git a/android/text/MeasuredText.java b/android/text/MeasuredText.java
index ffc44a7..3d9fba7 100644
--- a/android/text/MeasuredText.java
+++ b/android/text/MeasuredText.java
@@ -39,7 +39,6 @@
 
     private int mPos;
     private TextPaint mWorkPaint;
-    private StaticLayout.Builder mBuilder;
 
     private MeasuredText() {
         mWorkPaint = new TextPaint();
@@ -82,7 +81,6 @@
 
     void finish() {
         mText = null;
-        mBuilder = null;
         if (mLen > 1000) {
             mWidths = null;
             mChars = null;
@@ -93,9 +91,7 @@
     /**
      * Analyzes text for bidirectional runs.  Allocates working buffers.
      */
-    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
-            StaticLayout.Builder builder) {
-        mBuilder = builder;
+    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
         mText = text;
         mTextStart = start;
 
@@ -159,12 +155,12 @@
     /**
      * Apply the style.
      *
-     * If StaticLyaout.Builder is not provided in setPara() method, this method measures the styled
-     * text width.
-     * If StaticLayout.Builder is provided in setPara() method, this method just passes the style
-     * information to native code by calling StaticLayout.Builder.addstyleRun() and returns 0.
+     * If nativeStaticLayoutPtr is 0, this method measures the styled text width.
+     * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native
+     * code by calling StaticLayout.addstyleRun() and returns 0.
      */
-    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm,
+            long nativeStaticLayoutPtr) {
         if (fm != null) {
             paint.getFontMetricsInt(fm);
         }
@@ -174,10 +170,10 @@
 
         if (mEasy) {
             final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
-            if (mBuilder == null) {
+            if (nativeStaticLayoutPtr == 0) {
                 return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
             } else {
-                mBuilder.addStyleRun(paint, p, p + len, isRtl);
+                StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl);
                 return 0.0f;  // Builder.addStyleRun doesn't return the width.
             }
         }
@@ -187,12 +183,12 @@
         for (int q = p, i = p + 1, e = p + len;; ++i) {
             if (i == e || mLevels[i] != level) {
                 final boolean isRtl = (level & 0x1) != 0;
-                if (mBuilder == null) {
+                if (nativeStaticLayoutPtr == 0) {
                     totalAdvance +=
                             paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
                 } else {
                     // Builder.addStyleRun doesn't return the width.
-                    mBuilder.addStyleRun(paint, q, i, isRtl);
+                    StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl);
                 }
                 if (i == e) {
                     break;
@@ -201,11 +197,15 @@
                 level = mLevels[i];
             }
         }
-        return totalAdvance;  // If mBuilder is null, the result is zero.
+        return totalAdvance;  // If nativeStaticLayoutPtr is 0, the result is zero.
+    }
+
+    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+        return addStyleRun(paint, len, fm, 0 /* native ptr */);
     }
 
     float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
-            Paint.FontMetricsInt fm) {
+            Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) {
 
         TextPaint workPaint = mWorkPaint;
         workPaint.set(paint);
@@ -224,18 +224,18 @@
 
         float wid;
         if (replacement == null) {
-            wid = addStyleRun(workPaint, len, fm);
+            wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr);
         } else {
             // Use original text.  Shouldn't matter.
             wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
                     mTextStart + mPos + len, fm);
-            if (mBuilder == null) {
+            if (nativeStaticLayoutPtr == 0) {
                 float[] w = mWidths;
                 w[mPos] = wid;
                 for (int i = mPos + 1, e = mPos + len; i < e; i++)
                     w[i] = 0;
             } else {
-                mBuilder.addReplacementRun(paint, mPos, mPos + len, wid);
+                StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid);
             }
             mPos += len;
         }
@@ -253,6 +253,11 @@
         return wid;
     }
 
+    float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+            Paint.FontMetricsInt fm) {
+        return addStyleRun(paint, spans, len, fm, 0 /* native ptr */);
+    }
+
     int breakText(int limit, boolean forwards, float width) {
         float[] w = mWidths;
         if (forwards) {
diff --git a/android/text/StaticLayout.java b/android/text/StaticLayout.java
index 5c60188..c0fc44f 100644
--- a/android/text/StaticLayout.java
+++ b/android/text/StaticLayout.java
@@ -32,6 +32,9 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
 import java.util.Arrays;
 
 /**
@@ -57,9 +60,7 @@
      * default values.
      */
     public final static class Builder {
-        private Builder() {
-            mNativePtr = nNewBuilder();
-        }
+        private Builder() {}
 
         /**
          * Obtain a builder for constructing StaticLayout objects.
@@ -116,13 +117,11 @@
             b.mRightIndents = null;
             b.mLeftPaddings = null;
             b.mRightPaddings = null;
-            nFinishBuilder(b.mNativePtr);
             sPool.release(b);
         }
 
         // release any expensive state
         /* package */ void finish() {
-            nFinishBuilder(mNativePtr);
             mText = null;
             mPaint = null;
             mLeftIndents = null;
@@ -405,32 +404,6 @@
         }
 
         /**
-         * Measurement and break iteration is done in native code. The protocol for using
-         * the native code is as follows.
-         *
-         * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
-         * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
-         * future).
-         *
-         * Then, for each run within the paragraph:
-         *  - one of the following, depending on the type of run:
-         *    + addStyleRun (a text run, to be measured in native code)
-         *    + addReplacementRun (a replacement run, width is given)
-         *
-         * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
-         *
-         * After all paragraphs, call finish() to release expensive buffers.
-         */
-
-        /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
-            nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
-        }
-
-        /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
-            nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
-        }
-
-        /**
          * Build the {@link StaticLayout} after options have been set.
          *
          * <p>Note: the builder object must not be reused in any way after calling this
@@ -446,17 +419,6 @@
             return result;
         }
 
-        @Override
-        protected void finalize() throws Throwable {
-            try {
-                nFreeBuilder(mNativePtr);
-            } finally {
-                super.finalize();
-            }
-        }
-
-        /* package */ long mNativePtr;
-
         private CharSequence mText;
         private int mStart;
         private int mEnd;
@@ -694,270 +656,294 @@
             indents = null;
         }
 
-        int paraEnd;
-        for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
-            paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
-            if (paraEnd < 0)
-                paraEnd = bufEnd;
-            else
-                paraEnd++;
+        final long nativePtr = nInit(
+                b.mBreakStrategy, b.mHyphenationFrequency,
+                // TODO: Support more justification mode, e.g. letter spacing, stretching.
+                b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
+                indents, mLeftPaddings, mRightPaddings);
 
-            int firstWidthLineCount = 1;
-            int firstWidth = outerWidth;
-            int restWidth = outerWidth;
+        try {
+            int paraEnd;
+            for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
+                paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
+                if (paraEnd < 0) {
+                    paraEnd = bufEnd;
+                } else {
+                    paraEnd++;
+                }
 
-            LineHeightSpan[] chooseHt = null;
+                int firstWidthLineCount = 1;
+                int firstWidth = outerWidth;
+                int restWidth = outerWidth;
 
-            if (spanned != null) {
-                LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
-                        LeadingMarginSpan.class);
-                for (int i = 0; i < sp.length; i++) {
-                    LeadingMarginSpan lms = sp[i];
-                    firstWidth -= sp[i].getLeadingMargin(true);
-                    restWidth -= sp[i].getLeadingMargin(false);
+                LineHeightSpan[] chooseHt = null;
 
-                    // LeadingMarginSpan2 is odd.  The count affects all
-                    // leading margin spans, not just this particular one
-                    if (lms instanceof LeadingMarginSpan2) {
-                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
-                        firstWidthLineCount = Math.max(firstWidthLineCount,
-                                lms2.getLeadingMarginLineCount());
+                if (spanned != null) {
+                    LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+                            LeadingMarginSpan.class);
+                    for (int i = 0; i < sp.length; i++) {
+                        LeadingMarginSpan lms = sp[i];
+                        firstWidth -= sp[i].getLeadingMargin(true);
+                        restWidth -= sp[i].getLeadingMargin(false);
+
+                        // LeadingMarginSpan2 is odd.  The count affects all
+                        // leading margin spans, not just this particular one
+                        if (lms instanceof LeadingMarginSpan2) {
+                            LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+                            firstWidthLineCount = Math.max(firstWidthLineCount,
+                                    lms2.getLeadingMarginLineCount());
+                        }
+                    }
+
+                    chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+                    if (chooseHt.length == 0) {
+                        chooseHt = null; // So that out() would not assume it has any contents
+                    } else {
+                        if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+                            chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                        }
+
+                        for (int i = 0; i < chooseHt.length; i++) {
+                            int o = spanned.getSpanStart(chooseHt[i]);
+
+                            if (o < paraStart) {
+                                // starts in this layout, before the
+                                // current paragraph
+
+                                chooseHtv[i] = getLineTop(getLineForOffset(o));
+                            } else {
+                                // starts in this paragraph
+
+                                chooseHtv[i] = v;
+                            }
+                        }
                     }
                 }
 
-                chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+                measured.setPara(source, paraStart, paraEnd, textDir);
+                char[] chs = measured.mChars;
+                float[] widths = measured.mWidths;
+                byte[] chdirs = measured.mLevels;
+                int dir = measured.mDir;
+                boolean easy = measured.mEasy;
 
-                if (chooseHt.length == 0) {
-                    chooseHt = null; // So that out() would not assume it has any contents
-                } else {
-                    if (chooseHtv == null ||
-                        chooseHtv.length < chooseHt.length) {
-                        chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                // tab stop locations
+                int[] variableTabStops = null;
+                if (spanned != null) {
+                    TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+                            paraEnd, TabStopSpan.class);
+                    if (spans.length > 0) {
+                        int[] stops = new int[spans.length];
+                        for (int i = 0; i < spans.length; i++) {
+                            stops[i] = spans[i].getTabStop();
+                        }
+                        Arrays.sort(stops, 0, stops.length);
+                        variableTabStops = stops;
+                    }
+                }
+
+                // measurement has to be done before performing line breaking
+                // but we don't want to recompute fontmetrics or span ranges the
+                // second time, so we cache those and then use those stored values
+                int fmCacheCount = 0;
+                int spanEndCacheCount = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    if (fmCacheCount * 4 >= fmCache.length) {
+                        int[] grow = new int[fmCacheCount * 4 * 2];
+                        System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+                        fmCache = grow;
                     }
 
-                    for (int i = 0; i < chooseHt.length; i++) {
-                        int o = spanned.getSpanStart(chooseHt[i]);
+                    if (spanEndCacheCount >= spanEndCache.length) {
+                        int[] grow = new int[spanEndCacheCount * 2];
+                        System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+                        spanEndCache = grow;
+                    }
 
-                        if (o < paraStart) {
-                            // starts in this layout, before the
-                            // current paragraph
+                    if (spanned == null) {
+                        spanEnd = paraEnd;
+                        int spanLen = spanEnd - spanStart;
+                        measured.addStyleRun(paint, spanLen, fm, nativePtr);
+                    } else {
+                        spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+                                MetricAffectingSpan.class);
+                        int spanLen = spanEnd - spanStart;
+                        MetricAffectingSpan[] spans =
+                                spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+                        spans = TextUtils.removeEmptySpans(spans, spanned,
+                                MetricAffectingSpan.class);
+                        measured.addStyleRun(paint, spans, spanLen, fm, nativePtr);
+                    }
 
-                            chooseHtv[i] = getLineTop(getLineForOffset(o));
+                    // the order of storage here (top, bottom, ascent, descent) has to match the
+                    // code below where these values are retrieved
+                    fmCache[fmCacheCount * 4 + 0] = fm.top;
+                    fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+                    fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+                    fmCache[fmCacheCount * 4 + 3] = fm.descent;
+                    fmCacheCount++;
+
+                    spanEndCache[spanEndCacheCount] = spanEnd;
+                    spanEndCacheCount++;
+                }
+
+                int breakCount = nComputeLineBreaks(
+                        nativePtr,
+
+                        // Inputs
+                        chs,
+                        paraEnd - paraStart,
+                        firstWidth,
+                        firstWidthLineCount,
+                        restWidth,
+                        variableTabStops,
+                        TAB_INCREMENT,
+                        mLineCount,
+
+                        // Outputs
+                        lineBreaks,
+                        lineBreaks.breaks.length,
+                        lineBreaks.breaks,
+                        lineBreaks.widths,
+                        lineBreaks.ascents,
+                        lineBreaks.descents,
+                        lineBreaks.flags,
+                        widths);
+
+                final int[] breaks = lineBreaks.breaks;
+                final float[] lineWidths = lineBreaks.widths;
+                final float[] ascents = lineBreaks.ascents;
+                final float[] descents = lineBreaks.descents;
+                final int[] flags = lineBreaks.flags;
+
+                final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+                final boolean ellipsisMayBeApplied = ellipsize != null
+                        && (ellipsize == TextUtils.TruncateAt.END
+                            || (mMaximumVisibleLineCount == 1
+                                    && ellipsize != TextUtils.TruncateAt.MARQUEE));
+                if (0 < remainingLineCount && remainingLineCount < breakCount
+                        && ellipsisMayBeApplied) {
+                    // Calculate width and flag.
+                    float width = 0;
+                    int flag = 0; // XXX May need to also have starting hyphen edit
+                    for (int i = remainingLineCount - 1; i < breakCount; i++) {
+                        if (i == breakCount - 1) {
+                            width += lineWidths[i];
                         } else {
-                            // starts in this paragraph
-
-                            chooseHtv[i] = v;
+                            for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+                                width += widths[j];
+                            }
                         }
+                        flag |= flags[i] & TAB_MASK;
                     }
-                }
-            }
+                    // Treat the last line and overflowed lines as a single line.
+                    breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+                    lineWidths[remainingLineCount - 1] = width;
+                    flags[remainingLineCount - 1] = flag;
 
-            measured.setPara(source, paraStart, paraEnd, textDir, b);
-            char[] chs = measured.mChars;
-            float[] widths = measured.mWidths;
-            byte[] chdirs = measured.mLevels;
-            int dir = measured.mDir;
-            boolean easy = measured.mEasy;
-
-            // tab stop locations
-            int[] variableTabStops = null;
-            if (spanned != null) {
-                TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
-                        paraEnd, TabStopSpan.class);
-                if (spans.length > 0) {
-                    int[] stops = new int[spans.length];
-                    for (int i = 0; i < spans.length; i++) {
-                        stops[i] = spans[i].getTabStop();
-                    }
-                    Arrays.sort(stops, 0, stops.length);
-                    variableTabStops = stops;
-                }
-            }
-
-            nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
-                    firstWidth, firstWidthLineCount, restWidth,
-                    variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
-                    // TODO: Support more justification mode, e.g. letter spacing, stretching.
-                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
-                    // TODO: indents and paddings don't need to get passed to native code for every
-                    // paragraph. Pass them to native code just once.
-                    indents, mLeftPaddings, mRightPaddings, mLineCount);
-
-            // measurement has to be done before performing line breaking
-            // but we don't want to recompute fontmetrics or span ranges the
-            // second time, so we cache those and then use those stored values
-            int fmCacheCount = 0;
-            int spanEndCacheCount = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                if (fmCacheCount * 4 >= fmCache.length) {
-                    int[] grow = new int[fmCacheCount * 4 * 2];
-                    System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
-                    fmCache = grow;
+                    breakCount = remainingLineCount;
                 }
 
-                if (spanEndCacheCount >= spanEndCache.length) {
-                    int[] grow = new int[spanEndCacheCount * 2];
-                    System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
-                    spanEndCache = grow;
-                }
+                // here is the offset of the starting character of the line we are currently
+                // measuring
+                int here = paraStart;
 
-                if (spanned == null) {
-                    spanEnd = paraEnd;
-                    int spanLen = spanEnd - spanStart;
-                    measured.addStyleRun(paint, spanLen, fm);
-                } else {
-                    spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
-                            MetricAffectingSpan.class);
-                    int spanLen = spanEnd - spanStart;
-                    MetricAffectingSpan[] spans =
-                            spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
-                    spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
-                    measured.addStyleRun(paint, spans, spanLen, fm);
-                }
+                int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+                int fmCacheIndex = 0;
+                int spanEndCacheIndex = 0;
+                int breakIndex = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    // retrieve end of span
+                    spanEnd = spanEndCache[spanEndCacheIndex++];
 
-                // the order of storage here (top, bottom, ascent, descent) has to match the code below
-                // where these values are retrieved
-                fmCache[fmCacheCount * 4 + 0] = fm.top;
-                fmCache[fmCacheCount * 4 + 1] = fm.bottom;
-                fmCache[fmCacheCount * 4 + 2] = fm.ascent;
-                fmCache[fmCacheCount * 4 + 3] = fm.descent;
-                fmCacheCount++;
+                    // retrieve cached metrics, order matches above
+                    fm.top = fmCache[fmCacheIndex * 4 + 0];
+                    fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+                    fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+                    fm.descent = fmCache[fmCacheIndex * 4 + 3];
+                    fmCacheIndex++;
 
-                spanEndCache[spanEndCacheCount] = spanEnd;
-                spanEndCacheCount++;
-            }
-
-            int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
-                    lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
-                    lineBreaks.breaks.length, widths);
-
-            final int[] breaks = lineBreaks.breaks;
-            final float[] lineWidths = lineBreaks.widths;
-            final float[] ascents = lineBreaks.ascents;
-            final float[] descents = lineBreaks.descents;
-            final int[] flags = lineBreaks.flags;
-
-            final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
-            final boolean ellipsisMayBeApplied = ellipsize != null
-                    && (ellipsize == TextUtils.TruncateAt.END
-                        || (mMaximumVisibleLineCount == 1
-                                && ellipsize != TextUtils.TruncateAt.MARQUEE));
-            if (0 < remainingLineCount && remainingLineCount < breakCount
-                    && ellipsisMayBeApplied) {
-                // Calculate width and flag.
-                float width = 0;
-                int flag = 0; // XXX May need to also have starting hyphen edit
-                for (int i = remainingLineCount - 1; i < breakCount; i++) {
-                    if (i == breakCount - 1) {
-                        width += lineWidths[i];
-                    } else {
-                        for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
-                            width += widths[j];
-                        }
-                    }
-                    flag |= flags[i] & TAB_MASK;
-                }
-                // Treat the last line and overflowed lines as a single line.
-                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
-                lineWidths[remainingLineCount - 1] = width;
-                flags[remainingLineCount - 1] = flag;
-
-                breakCount = remainingLineCount;
-            }
-
-            // here is the offset of the starting character of the line we are currently measuring
-            int here = paraStart;
-
-            int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
-            int fmCacheIndex = 0;
-            int spanEndCacheIndex = 0;
-            int breakIndex = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                // retrieve end of span
-                spanEnd = spanEndCache[spanEndCacheIndex++];
-
-                // retrieve cached metrics, order matches above
-                fm.top = fmCache[fmCacheIndex * 4 + 0];
-                fm.bottom = fmCache[fmCacheIndex * 4 + 1];
-                fm.ascent = fmCache[fmCacheIndex * 4 + 2];
-                fm.descent = fmCache[fmCacheIndex * 4 + 3];
-                fmCacheIndex++;
-
-                if (fm.top < fmTop) {
-                    fmTop = fm.top;
-                }
-                if (fm.ascent < fmAscent) {
-                    fmAscent = fm.ascent;
-                }
-                if (fm.descent > fmDescent) {
-                    fmDescent = fm.descent;
-                }
-                if (fm.bottom > fmBottom) {
-                    fmBottom = fm.bottom;
-                }
-
-                // skip breaks ending before current span range
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
-                    breakIndex++;
-                }
-
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
-                    int endPos = paraStart + breaks[breakIndex];
-
-                    boolean moreChars = (endPos < bufEnd);
-
-                    final int ascent = fallbackLineSpacing
-                            ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
-                            : fmAscent;
-                    final int descent = fallbackLineSpacing
-                            ? Math.max(fmDescent, Math.round(descents[breakIndex]))
-                            : fmDescent;
-                    v = out(source, here, endPos,
-                            ascent, descent, fmTop, fmBottom,
-                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
-                            needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
-                            addLastLineSpacing, chs, widths, paraStart, ellipsize,
-                            ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
-
-                    if (endPos < spanEnd) {
-                        // preserve metrics for current span
+                    if (fm.top < fmTop) {
                         fmTop = fm.top;
-                        fmBottom = fm.bottom;
+                    }
+                    if (fm.ascent < fmAscent) {
                         fmAscent = fm.ascent;
+                    }
+                    if (fm.descent > fmDescent) {
                         fmDescent = fm.descent;
-                    } else {
-                        fmTop = fmBottom = fmAscent = fmDescent = 0;
+                    }
+                    if (fm.bottom > fmBottom) {
+                        fmBottom = fm.bottom;
                     }
 
-                    here = endPos;
-                    breakIndex++;
-
-                    if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
-                        return;
+                    // skip breaks ending before current span range
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+                        breakIndex++;
                     }
+
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+                        int endPos = paraStart + breaks[breakIndex];
+
+                        boolean moreChars = (endPos < bufEnd);
+
+                        final int ascent = fallbackLineSpacing
+                                ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+                                : fmAscent;
+                        final int descent = fallbackLineSpacing
+                                ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+                                : fmDescent;
+                        v = out(source, here, endPos,
+                                ascent, descent, fmTop, fmBottom,
+                                v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+                                flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd,
+                                includepad, trackpad, addLastLineSpacing, chs, widths, paraStart,
+                                ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint,
+                                moreChars);
+
+                        if (endPos < spanEnd) {
+                            // preserve metrics for current span
+                            fmTop = fm.top;
+                            fmBottom = fm.bottom;
+                            fmAscent = fm.ascent;
+                            fmDescent = fm.descent;
+                        } else {
+                            fmTop = fmBottom = fmAscent = fmDescent = 0;
+                        }
+
+                        here = endPos;
+                        breakIndex++;
+
+                        if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+                            return;
+                        }
+                    }
+                }
+
+                if (paraEnd == bufEnd) {
+                    break;
                 }
             }
 
-            if (paraEnd == bufEnd)
-                break;
-        }
+            if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
+                    && mLineCount < mMaximumVisibleLineCount) {
+                measured.setPara(source, bufEnd, bufEnd, textDir);
 
-        if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
-                mLineCount < mMaximumVisibleLineCount) {
-            measured.setPara(source, bufEnd, bufEnd, textDir, b);
+                paint.getFontMetricsInt(fm);
 
-            paint.getFontMetricsInt(fm);
-
-            v = out(source,
-                    bufEnd, bufEnd, fm.ascent, fm.descent,
-                    fm.top, fm.bottom,
-                    v,
-                    spacingmult, spacingadd, null,
-                    null, fm, 0,
-                    needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
-                    includepad, trackpad, addLastLineSpacing, null,
-                    null, bufStart, ellipsize,
-                    ellipsizedWidth, 0, paint, false);
+                v = out(source,
+                        bufEnd, bufEnd, fm.ascent, fm.descent,
+                        fm.top, fm.bottom,
+                        v,
+                        spacingmult, spacingadd, null,
+                        null, fm, 0,
+                        needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
+                        includepad, trackpad, addLastLineSpacing, null,
+                        null, bufStart, ellipsize,
+                        ellipsizedWidth, 0, paint, false);
+            }
+        } finally {
+            nFinish(nativePtr);
         }
     }
 
@@ -1487,26 +1473,51 @@
                 mMaxLineHeight : super.getHeight();
     }
 
-    private static native long nNewBuilder();
-    private static native void nFreeBuilder(long nativePtr);
-    private static native void nFinishBuilder(long nativePtr);
+    /**
+     * Measurement and break iteration is done in native code. The protocol for using
+     * the native code is as follows.
+     *
+     * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
+     * following:
+     *
+     *   - Call one of the following methods for each run within the paragraph depending on the type
+     *     of run:
+     *     + addStyleRun (a text run, to be measured in native code)
+     *     + addReplacementRun (a replacement run, width is given)
+     *
+     *   - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+     *
+     * After all paragraphs, call finish() to release expensive buffers.
+     */
 
-    // Set up paragraph text and settings; done as one big method to minimize jni crossings
-    private static native void nSetupParagraph(
-            /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
-            @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
-            @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
-            int defaultTabStop, @BreakStrategy int breakStrategy,
-            @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
-            @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
-            @IntRange(from = 0) int indentsOffset);
+    /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end,
+            boolean isRtl) {
+        nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl);
+    }
 
-    // TODO: Make this method CriticalNative once native code defers doing layouts.
+    /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end,
+            float width) {
+        nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width);
+    }
+
+    @FastNative
+    private static native long nInit(
+            @BreakStrategy int breakStrategy,
+            @HyphenationFrequency int hyphenationFrequency,
+            boolean isJustified,
+            @Nullable int[] indents,
+            @Nullable int[] leftPaddings,
+            @Nullable int[] rightPaddings);
+
+    @CriticalNative
+    private static native void nFinish(long nativePtr);
+
+    @CriticalNative
     private static native void nAddStyleRun(
             /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
 
-    // TODO: Make this method CriticalNative once native code defers doing layouts.
+    @CriticalNative
     private static native void nAddReplacementRun(
             /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end,
@@ -1519,10 +1530,28 @@
     // arrays do not have to be resized
     // The individual character widths will be returned in charWidths. The length of charWidths must
     // be at least the length of the text.
-    private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
-            int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
-            float[] recycleDescents, int[] recycleFlags, int recycleLength,
-            float[] charWidths);
+    private static native int nComputeLineBreaks(
+            /* non zero */ long nativePtr,
+
+            // Inputs
+            @NonNull char[] text,
+            @IntRange(from = 0) int length,
+            @FloatRange(from = 0.0f) float firstWidth,
+            @IntRange(from = 0) int firstWidthLineCount,
+            @FloatRange(from = 0.0f) float restWidth,
+            @Nullable int[] variableTabStops,
+            int defaultTabStop,
+            @IntRange(from = 0) int indentsOffset,
+
+            // Outputs
+            @NonNull LineBreaks recycle,
+            @IntRange(from  = 0) int recycleLength,
+            @NonNull int[] recycleBreaks,
+            @NonNull float[] recycleWidths,
+            @NonNull float[] recycleAscents,
+            @NonNull float[] recycleDescents,
+            @NonNull int[] recycleFlags,
+            @NonNull float[] charWidths);
 
     private int mLineCount;
     private int mTopPadding, mBottomPadding;
diff --git a/android/text/StaticLayout_Delegate.java b/android/text/StaticLayout_Delegate.java
index def3c91..ef7525e 100644
--- a/android/text/StaticLayout_Delegate.java
+++ b/android/text/StaticLayout_Delegate.java
@@ -4,12 +4,14 @@
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.BidiRenderer;
 import android.graphics.Paint;
 import android.graphics.Paint_Delegate;
 import android.graphics.RectF;
 import android.icu.text.BreakIterator;
-import android.icu.util.ULocale;
+import android.text.Layout.BreakStrategy;
+import android.text.Layout.HyphenationFrequency;
 import android.text.Primitive.PrimitiveType;
 import android.text.StaticLayout.LineBreaks;
 
@@ -38,35 +40,21 @@
         new DelegateManager<Builder>(Builder.class);
 
     @LayoutlibDelegate
-    /*package*/ static long nNewBuilder() {
-        return sBuilderManager.addNewDelegate(new Builder());
+    /*package*/ static long nInit(
+            @BreakStrategy int breakStrategy,
+            @HyphenationFrequency int hyphenationFrequency,
+            boolean isJustified,
+            @Nullable int[] indents,
+            @Nullable int[] leftPaddings,
+            @Nullable int[] rightPaddings) {
+        Builder builder = new Builder();
+        builder.mBreakStrategy = breakStrategy;
+        return sBuilderManager.addNewDelegate(builder);
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nFreeBuilder(long nativeBuilder) {
-        sBuilderManager.removeJavaReferenceFor(nativeBuilder);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nFinishBuilder(long nativeBuilder) {
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static void nSetupParagraph(long nativeBuilder, char[] text, int length,
-            float firstWidth, int firstWidthLineCount, float restWidth,
-            int[] variableTabStops, int defaultTabStop, int breakStrategy,
-            int hyphenationFrequency, boolean isJustified, int[] indents, int[] leftPaddings,
-            int[] rightPaddings, int intentsOffset) {
-        // TODO: implement justified alignment
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
-        if (builder == null) {
-            return;
-        }
-
-        builder.mText = text;
-        builder.mWidths = new float[length];
-        builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
-        builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
+    /*package*/ static void nFinish(long nativePtr) {
+        sBuilderManager.removeJavaReferenceFor(nativePtr);
     }
 
     @LayoutlibDelegate
@@ -76,10 +64,7 @@
         if (builder == null) {
             return;
         }
-
-        int bidiFlags = isRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
-        measureText(nativePaint, builder.mText, start, end - start, builder.mWidths,
-                bidiFlags);
+        builder.mRuns.add(new StyleRun(nativePaint, start, end, isRtl));
     }
 
     @LayoutlibDelegate
@@ -89,22 +74,47 @@
         if (builder == null) {
             return;
         }
-        builder.mWidths[start] = width;
-        Arrays.fill(builder.mWidths, start + 1, end, 0.0f);
+        builder.mRuns.add(new ReplacementRun(start, end, width));
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nComputeLineBreaks(long nativeBuilder, LineBreaks recycle,
-            int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
-            float[] recycleDescents, int[] recycleFlags, int recycleLength, float[] charWidths) {
+    /*package*/ static int nComputeLineBreaks(
+            /* non zero */ long nativePtr,
 
-        Builder builder = sBuilderManager.getDelegate(nativeBuilder);
+            // Inputs
+            @NonNull char[] text,
+            int length,
+            float firstWidth,
+            int firstWidthLineCount,
+            float restWidth,
+            @Nullable int[] variableTabStops,
+            int defaultTabStop,
+            int indentsOffset,
+
+            // Outputs
+            @NonNull LineBreaks recycle,
+            int recycleLength,
+            @NonNull int[] recycleBreaks,
+            @NonNull float[] recycleWidths,
+            @NonNull float[] recycleAscents,
+            @NonNull float[] recycleDescents,
+            @NonNull int[] recycleFlags,
+            @NonNull float[] charWidths) {
+        Builder builder = sBuilderManager.getDelegate(nativePtr);
         if (builder == null) {
             return 0;
         }
 
+        builder.mText = text;
+        builder.mWidths = new float[length];
+        builder.mLineWidth = new LineWidth(firstWidth, firstWidthLineCount, restWidth);
+        builder.mTabStopCalculator = new TabStops(variableTabStops, defaultTabStop);
+
+        for (Run run: builder.mRuns) {
+            run.addTo(builder);
+        }
+
         // compute all possible breakpoints.
-        int length = builder.mWidths.length;
         BreakIterator it = BreakIterator.getLineInstance();
         it.setText(new Segment(builder.mText, 0, length));
 
@@ -196,9 +206,55 @@
     private static class Builder {
         char[] mText;
         float[] mWidths;
-        LineBreaker mLineBreaker;
-        int mBreakStrategy;
-        LineWidth mLineWidth;
-        TabStops mTabStopCalculator;
+        private LineBreaker mLineBreaker;
+        private int mBreakStrategy;
+        private LineWidth mLineWidth;
+        private TabStops mTabStopCalculator;
+        private ArrayList<Run> mRuns = new ArrayList<>();
+    }
+
+    private abstract static class Run {
+        int mStart;
+        int mEnd;
+
+        Run(int start, int end) {
+            mStart = start;
+            mEnd = end;
+        }
+
+        abstract void addTo(Builder builder);
+    }
+
+    private static class StyleRun extends Run {
+        private long mNativePaint;
+        private boolean mIsRtl;
+
+        private StyleRun(long nativePaint, int start, int end, boolean isRtl) {
+            super(start, end);
+            mNativePaint = nativePaint;
+            mIsRtl = isRtl;
+        }
+
+        @Override
+        void addTo(Builder builder) {
+            int bidiFlags = mIsRtl ? Paint.BIDI_FORCE_RTL : Paint.BIDI_FORCE_LTR;
+            measureText(mNativePaint, builder.mText, mStart, mEnd - mStart, builder.mWidths,
+                    bidiFlags);
+        }
+    }
+
+    private static class ReplacementRun extends Run {
+        private final float mWidth;
+
+        private ReplacementRun(int start, int end, float width) {
+            super(start, end);
+            mWidth = width;
+        }
+
+        @Override
+        void addTo(Builder builder) {
+            builder.mWidths[mStart] = mWidth;
+            Arrays.fill(builder.mWidths, mStart + 1, mEnd, 0.0f);
+        }
     }
 }
diff --git a/android/text/TextLine.java b/android/text/TextLine.java
index 20c0ed8..86cc014 100644
--- a/android/text/TextLine.java
+++ b/android/text/TextLine.java
@@ -28,6 +28,7 @@
 import android.text.style.ReplacementSpan;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
@@ -44,7 +45,8 @@
  *
  * @hide
  */
-class TextLine {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class TextLine {
     private static final boolean DEBUG = false;
 
     private TextPaint mPaint;
@@ -82,7 +84,8 @@
      *
      * @return an uninitialized TextLine
      */
-    static TextLine obtain() {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static TextLine obtain() {
         TextLine tl;
         synchronized (sCached) {
             for (int i = sCached.length; --i >= 0;) {
@@ -107,7 +110,8 @@
      * @return null, as a convenience from clearing references to the provided
      * TextLine
      */
-    static TextLine recycle(TextLine tl) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static TextLine recycle(TextLine tl) {
         tl.mText = null;
         tl.mPaint = null;
         tl.mDirections = null;
@@ -142,7 +146,8 @@
      * @param hasTabs true if the line might contain tabs
      * @param tabStops the tabStops. Can be null.
      */
-    void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
             Directions directions, boolean hasTabs, TabStops tabStops) {
         mPaint = paint;
         mText = text;
@@ -196,7 +201,8 @@
     /**
      * Justify the line to the given width.
      */
-    void justify(float justifyWidth) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void justify(float justifyWidth) {
         int end = mLen;
         while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
             end--;
@@ -277,7 +283,8 @@
      * @param fmi receives font metrics information, can be null
      * @return the signed width of the line
      */
-    float metrics(FontMetricsInt fmi) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public float metrics(FontMetricsInt fmi) {
         return measure(mLen, false, fmi);
     }
 
@@ -1165,23 +1172,18 @@
     }
 
     private boolean isStretchableWhitespace(int ch) {
-        // TODO: Support other stretchable whitespace. (Bug: 34013491)
-        return ch == 0x0020 || ch == 0x00A0;
-    }
-
-    private int nextStretchableSpace(int start, int end) {
-        for (int i = start; i < end; i++) {
-            final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
-            if (isStretchableWhitespace(c)) return i;
-        }
-        return end;
+        // TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709).
+        return ch == 0x0020;
     }
 
     /* Return the number of spaces in the text line, for the purpose of justification */
     private int countStretchableSpaces(int start, int end) {
         int count = 0;
-        for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) {
-            count++;
+        for (int i = start; i < end; i++) {
+            final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
+            if (isStretchableWhitespace(c)) {
+                count++;
+            }
         }
         return count;
     }
diff --git a/android/text/TextUtils.java b/android/text/TextUtils.java
index 68afeec..cbdaa69 100644
--- a/android/text/TextUtils.java
+++ b/android/text/TextUtils.java
@@ -1519,7 +1519,7 @@
                     }
 
                     // XXX this is probably ok, but need to look at it more
-                    tempMt.setPara(format, 0, format.length(), textDir, null);
+                    tempMt.setPara(format, 0, format.length(), textDir);
                     float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
 
                     if (w + moreWid <= avail) {
@@ -1541,7 +1541,7 @@
     private static float setPara(MeasuredText mt, TextPaint paint,
             CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
 
-        mt.setPara(text, start, end, textDir, null);
+        mt.setPara(text, start, end, textDir);
 
         float width;
         Spanned sp = text instanceof Spanned ? (Spanned) text : null;
diff --git a/android/util/Log.java b/android/util/Log.java
index b94e48b..0299865 100644
--- a/android/util/Log.java
+++ b/android/util/Log.java
@@ -16,12 +16,45 @@
 
 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;
 
 /**
- * Mock Log implementation for testing on non android host.
+ * 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.
  */
 public final class Log {
 
@@ -55,6 +88,29 @@
      */
     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() {
     }
 
@@ -65,7 +121,7 @@
      * @param msg The message you would like logged.
      */
     public static int v(String tag, String msg) {
-        return println(LOG_ID_MAIN, VERBOSE, tag, msg);
+        return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
     }
 
     /**
@@ -76,7 +132,7 @@
      * @param tr An exception to log
      */
     public static int v(String tag, String msg, Throwable tr) {
-        return println(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
     }
 
     /**
@@ -86,7 +142,7 @@
      * @param msg The message you would like logged.
      */
     public static int d(String tag, String msg) {
-        return println(LOG_ID_MAIN, DEBUG, tag, msg);
+        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
     }
 
     /**
@@ -97,7 +153,7 @@
      * @param tr An exception to log
      */
     public static int d(String tag, String msg, Throwable tr) {
-        return println(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
     }
 
     /**
@@ -107,7 +163,7 @@
      * @param msg The message you would like logged.
      */
     public static int i(String tag, String msg) {
-        return println(LOG_ID_MAIN, INFO, tag, msg);
+        return println_native(LOG_ID_MAIN, INFO, tag, msg);
     }
 
     /**
@@ -118,7 +174,7 @@
      * @param tr An exception to log
      */
     public static int i(String tag, String msg, Throwable tr) {
-        return println(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, INFO, tag, msg, tr);
     }
 
     /**
@@ -128,7 +184,7 @@
      * @param msg The message you would like logged.
      */
     public static int w(String tag, String msg) {
-        return println(LOG_ID_MAIN, WARN, tag, msg);
+        return println_native(LOG_ID_MAIN, WARN, tag, msg);
     }
 
     /**
@@ -139,9 +195,31 @@
      * @param tr An exception to log
      */
     public static int w(String tag, String msg, Throwable tr) {
-        return println(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, WARN, tag, msg, 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
@@ -149,7 +227,7 @@
      * @param tr An exception to log
      */
     public static int w(String tag, Throwable tr) {
-        return println(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
+        return printlns(LOG_ID_MAIN, WARN, tag, "", tr);
     }
 
     /**
@@ -159,7 +237,7 @@
      * @param msg The message you would like logged.
      */
     public static int e(String tag, String msg) {
-        return println(LOG_ID_MAIN, ERROR, tag, msg);
+        return println_native(LOG_ID_MAIN, ERROR, tag, msg);
     }
 
     /**
@@ -170,7 +248,82 @@
      * @param tr An exception to log
      */
     public static int e(String tag, String msg, Throwable tr) {
-        return println(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(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;
     }
 
     /**
@@ -193,7 +346,7 @@
         }
 
         StringWriter sw = new StringWriter();
-        PrintWriter pw = new PrintWriter(sw);
+        PrintWriter pw = new FastPrintWriter(sw, false, 256);
         tr.printStackTrace(pw);
         pw.flush();
         return sw.toString();
@@ -208,7 +361,7 @@
      * @return The number of bytes written.
      */
     public static int println(int priority, String tag, String msg) {
-        return println(LOG_ID_MAIN, priority, tag, msg);
+        return println_native(LOG_ID_MAIN, priority, tag, msg);
     }
 
     /** @hide */ public static final int LOG_ID_MAIN = 0;
@@ -217,9 +370,115 @@
     /** @hide */ public static final int LOG_ID_SYSTEM = 3;
     /** @hide */ public static final int LOG_ID_CRASH = 4;
 
-    /** @hide */ @SuppressWarnings("unused")
-    public static int println(int bufID,
-            int priority, String tag, String msg) {
-        return 0;
+    /** @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.
+        }
     }
 }
diff --git a/android/util/LruCache.java b/android/util/LruCache.java
index 5208606..4015488 100644
--- a/android/util/LruCache.java
+++ b/android/util/LruCache.java
@@ -20,10 +20,6 @@
 import java.util.Map;
 
 /**
- * BEGIN LAYOUTLIB CHANGE
- * This is a custom version that doesn't use the non standard LinkedHashMap#eldest.
- * END LAYOUTLIB CHANGE
- *
  * A cache that holds strong references to a limited number of values. Each time
  * a value is accessed, it is moved to the head of a queue. When a value is
  * added to a full cache, the value at the end of that queue is evicted and may
@@ -91,9 +87,8 @@
 
     /**
      * Sets the size of the cache.
-     * @param maxSize The new maximum size.
      *
-     * @hide
+     * @param maxSize The new maximum size.
      */
     public void resize(int maxSize) {
         if (maxSize <= 0) {
@@ -190,10 +185,13 @@
     }
 
     /**
+     * Remove the eldest entries until the total of remaining entries is at or
+     * below the requested size.
+     *
      * @param maxSize the maximum size of the cache before returning. May be -1
-     *     to evict even 0-sized elements.
+     *            to evict even 0-sized elements.
      */
-    private void trimToSize(int maxSize) {
+    public void trimToSize(int maxSize) {
         while (true) {
             K key;
             V value;
@@ -207,16 +205,7 @@
                     break;
                 }
 
-                // BEGIN LAYOUTLIB CHANGE
-                // get the last item in the linked list.
-                // This is not efficient, the goal here is to minimize the changes
-                // compared to the platform version.
-                Map.Entry<K, V> toEvict = null;
-                for (Map.Entry<K, V> entry : map.entrySet()) {
-                    toEvict = entry;
-                }
-                // END LAYOUTLIB CHANGE
-
+                Map.Entry<K, V> toEvict = map.eldest();
                 if (toEvict == null) {
                     break;
                 }
diff --git a/android/view/RectShadowPainter.java b/android/view/RectShadowPainter.java
index 5665d4f..88771a7 100644
--- a/android/view/RectShadowPainter.java
+++ b/android/view/RectShadowPainter.java
@@ -19,8 +19,10 @@
 import com.android.layoutlib.bridge.impl.GcSnapshot;
 import com.android.layoutlib.bridge.impl.ResourceHelper;
 
+import android.graphics.BaseCanvas_Delegate;
 import android.graphics.Canvas;
 import android.graphics.Canvas_Delegate;
+import android.graphics.Color;
 import android.graphics.LinearGradient;
 import android.graphics.Outline;
 import android.graphics.Paint;
@@ -46,7 +48,8 @@
     private static final int END_COLOR = ResourceHelper.getColor("#03000000");
     private static final float PERPENDICULAR_ANGLE = 90f;
 
-    public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+    public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas,
+            float alpha) {
         Rect outline = new Rect();
         if (!viewOutline.getRect(outline)) {
             assert false : "Outline is not a rect shadow";
@@ -74,9 +77,16 @@
             edgePaint.setAntiAlias(false);
             float outerArcRadius = radius + shadowSize;
             int[] colors = {START_COLOR, START_COLOR, END_COLOR};
+            if (alpha != 1f) {
+                // Correct colors using the given component alpha
+                for (int i = 0; i < colors.length; i++) {
+                    colors[i] = Color.argb((int) (Color.alpha(colors[i]) * alpha), Color.red(colors[i]),
+                            Color.green(colors[i]), Color.blue(colors[i]));
+                }
+            }
             cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
                     new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
-            edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, START_COLOR, END_COLOR,
+            edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, colors[0], colors[2],
                     TileMode.CLAMP));
             Path path = new Path();
             path.setFillType(FillType.EVEN_ODD);
@@ -184,7 +194,8 @@
     /**
      * Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks.
      * <p/>
-     * This is required because {@link Canvas_Delegate#native_drawRect(long, float, float, float,
+     * This is required because {@link BaseCanvas_Delegate#native_drawRect(long, float, float,
+     * float,
      * float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up
      * drawing empty rectangles, which results in IllegalArgumentException.
      */
diff --git a/android/view/ShadowPainter.java b/android/view/ShadowPainter.java
index f09fffd..788c6c3 100644
--- a/android/view/ShadowPainter.java
+++ b/android/view/ShadowPainter.java
@@ -41,14 +41,16 @@
      * @param source the source image
      * @param shadowSize the size of the shadow, normally {@link #SHADOW_SIZE or {@link
      * #SMALL_SHADOW_SIZE}}
+     * @param alpha alpha value to apply to the shadow
      *
      * @return an image with the shadow painted in or the source image if shadowSize <= 1
      */
     @NonNull
-    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize) {
+    public static BufferedImage createDropShadow(BufferedImage source, int shadowSize, float
+            alpha) {
         shadowSize /= 2; // make shadow size have the same meaning as in the other shadow paint methods in this class
 
-        return createDropShadow(source, shadowSize, 0.7f, 0);
+        return createDropShadow(source, shadowSize, 0.7f * alpha, 0);
     }
 
     /**
diff --git a/android/view/SurfaceControl.java b/android/view/SurfaceControl.java
index ff027a9..6f8315a 100644
--- a/android/view/SurfaceControl.java
+++ b/android/view/SurfaceControl.java
@@ -16,27 +16,22 @@
 
 package android.view;
 
-import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-
 import android.annotation.Size;
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
-import android.graphics.Point;
-import android.graphics.PointF;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Binder;
-import android.os.Debug;
 import android.os.IBinder;
+import android.os.Process;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.Surface.OutOfResourcesException;
-
 import dalvik.system.CloseGuard;
+import libcore.util.NativeAllocationRegistry;
 
 import java.io.Closeable;
 
-import libcore.util.NativeAllocationRegistry;
-
 /**
  * SurfaceControl
  *  @hide
@@ -60,6 +55,8 @@
     private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
             Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
             boolean allLayers, boolean useIdentityTransform);
+    private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer,
+            int rotation);
 
     private static native long nativeCreateTransaction();
     private static native long nativeGetNativeTransactionFinalizer();
@@ -186,7 +183,7 @@
 
     /**
      * Surface creation flag: Indicates that the surface must be considered opaque,
-     * even if its pixel format is set to translucent. This can be useful if an
+     * even if its pixel format contains an alpha channel. This can be useful if an
      * application needs full RGBA 8888 support for instance but will
      * still draw every pixel opaque.
      * <p>
@@ -307,6 +304,203 @@
     public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
 
     /**
+     * Builder class for {@link SurfaceControl} objects.
+     */
+    public static class Builder {
+        private SurfaceSession mSession;
+        private int mFlags = HIDDEN;
+        private int mWidth;
+        private int mHeight;
+        private int mFormat = PixelFormat.OPAQUE;
+        private String mName;
+        private SurfaceControl mParent;
+        private int mWindowType;
+        private int mOwnerUid;
+
+        /**
+         * Begin building a SurfaceControl with a given {@link SurfaceSession}.
+         *
+         * @param session The {@link SurfaceSession} with which to eventually construct the surface.
+         */
+        public Builder(SurfaceSession session) {
+            mSession = session;
+        }
+
+        /**
+         * Construct a new {@link SurfaceControl} with the set parameters.
+         */
+        public SurfaceControl build() {
+            if (mWidth <= 0 || mHeight <= 0) {
+                throw new IllegalArgumentException(
+                        "width and height must be set");
+            }
+            return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat,
+                    mFlags, mParent, mWindowType, mOwnerUid);
+        }
+
+        /**
+         * Set a debugging-name for the SurfaceControl.
+         *
+         * @param name A name to identify the Surface in debugging.
+         */
+        public Builder setName(String name) {
+            mName = name;
+            return this;
+        }
+
+        /**
+         * Set the initial size of the controlled surface's buffers in pixels.
+         *
+         * @param width The buffer width in pixels.
+         * @param height The buffer height in pixels.
+         */
+        public Builder setSize(int width, int height) {
+            if (width <= 0 || height <= 0) {
+                throw new IllegalArgumentException(
+                        "width and height must be positive");
+            }
+            mWidth = width;
+            mHeight = height;
+            return this;
+        }
+
+        /**
+         * Set the pixel format of the controlled surface's buffers, using constants from
+         * {@link android.graphics.PixelFormat}.
+         */
+        public Builder setFormat(@PixelFormat.Format int format) {
+            mFormat = format;
+            return this;
+        }
+
+        /**
+         * Specify if the app requires a hardware-protected path to
+         * an external display sync. If protected content is enabled, but
+         * such a path is not available, then the controlled Surface will
+         * not be displayed.
+         *
+         * @param protectedContent Whether to require a protected sink.
+         */
+        public Builder setProtected(boolean protectedContent) {
+            if (protectedContent) {
+                mFlags |= PROTECTED_APP;
+            } else {
+                mFlags &= ~PROTECTED_APP;
+            }
+            return this;
+        }
+
+        /**
+         * Specify whether the Surface contains secure content. If true, the system
+         * will prevent the surfaces content from being copied by another process. In
+         * particular screenshots and VNC servers will be disabled. This is however
+         * not a complete prevention of readback as {@link #setProtected}.
+         */
+        public Builder setSecure(boolean secure) {
+            if (secure) {
+                mFlags |= SECURE;
+            } else {
+                mFlags &= ~SECURE;
+            }
+            return this;
+        }
+
+        /**
+         * Indicates whether the surface must be considered opaque,
+         * even if its pixel format is set to translucent. This can be useful if an
+         * application needs full RGBA 8888 support for instance but will
+         * still draw every pixel opaque.
+         * <p>
+         * This flag only determines whether opacity will be sampled from the alpha channel.
+         * Plane-alpha from calls to setAlpha() can still result in blended composition
+         * regardless of the opaque setting.
+         *
+         * Combined effects are (assuming a buffer format with an alpha channel):
+         * <ul>
+         * <li>OPAQUE + alpha(1.0) == opaque composition
+         * <li>OPAQUE + alpha(0.x) == blended composition
+         * <li>OPAQUE + alpha(0.0) == no composition
+         * <li>!OPAQUE + alpha(1.0) == blended composition
+         * <li>!OPAQUE + alpha(0.x) == blended composition
+         * <li>!OPAQUE + alpha(0.0) == no composition
+         * </ul>
+         * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
+         * were set automatically.
+         * @param opaque Whether the Surface is OPAQUE.
+         */
+        public Builder setOpaque(boolean opaque) {
+            if (opaque) {
+                mFlags |= OPAQUE;
+            } else {
+                mFlags &= ~OPAQUE;
+            }
+            return this;
+        }
+
+        /**
+         * Set a parent surface for our new SurfaceControl.
+         *
+         * Child surfaces are constrained to the onscreen region of their parent.
+         * Furthermore they stack relatively in Z order, and inherit the transformation
+         * of the parent.
+         *
+         * @param parent The parent control.
+         */
+        public Builder setParent(SurfaceControl parent) {
+            mParent = parent;
+            return this;
+        }
+
+        /**
+         * Set surface metadata.
+         *
+         * Currently these are window-types as per {@link WindowManager.LayoutParams} and
+         * owner UIDs. Child surfaces inherit their parents
+         * metadata so only the WindowManager needs to set this on root Surfaces.
+         *
+         * @param windowType A window-type
+         * @param ownerUid UID of the window owner.
+         */
+        public Builder setMetadata(int windowType, int ownerUid) {
+            if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
+                throw new UnsupportedOperationException(
+                        "It only makes sense to set Surface metadata from the WindowManager");
+            }
+            mWindowType = windowType;
+            mOwnerUid = ownerUid;
+            return this;
+        }
+
+        /**
+         * Indicate whether a 'ColorLayer' is to be constructed.
+         *
+         * Color layers will not have an associated BufferQueue and will instead always render a
+         * solid color (that is, solid before plane alpha). Currently that color is black.
+         *
+         * @param isColorLayer Whether to create a color layer.
+         */
+        public Builder setColorLayer(boolean isColorLayer) {
+            if (isColorLayer) {
+                mFlags |= FX_SURFACE_DIM;
+            } else {
+                mFlags &= ~FX_SURFACE_DIM;
+            }
+            return this;
+        }
+
+        /**
+         * Set 'Surface creation flags' such as {@link HIDDEN}, {@link SECURE}.
+         *
+         * TODO: Finish conversion to individual builder methods?
+         * @param flags The combined flags
+         */
+        public Builder setFlags(int flags) {
+            mFlags = flags;
+            return this;
+        }
+    }
+
+    /**
      * Create a surface with a name.
      * <p>
      * The surface creation flags specify what kind of surface to create and
@@ -331,19 +525,7 @@
      *
      * @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
      */
-    public SurfaceControl(SurfaceSession session,
-            String name, int w, int h, int format, int flags, int windowType, int ownerUid)
-                    throws OutOfResourcesException {
-        this(session, name, w, h, format, flags, null, windowType, ownerUid);
-    }
-
-    public SurfaceControl(SurfaceSession session,
-            String name, int w, int h, int format, int flags)
-                    throws OutOfResourcesException {
-        this(session, name, w, h, format, flags, null, INVALID_WINDOW_TYPE, Binder.getCallingUid());
-    }
-
-    public SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
+    private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
             SurfaceControl parent, int windowType, int ownerUid)
                     throws OutOfResourcesException {
         if (session == null) {
@@ -533,7 +715,7 @@
         }
     }
 
-    public void setRelativeLayer(IBinder relativeTo, int zorder) {
+    public void setRelativeLayer(SurfaceControl relativeTo, int zorder) {
         checkNotReleased();
         synchronized(SurfaceControl.class) {
             sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
@@ -970,6 +1152,20 @@
                 minLayer, maxLayer, allLayers, useIdentityTransform);
     }
 
+    /**
+     * Captures a layer and its children into the provided {@link Surface}.
+     *
+     * @param layerHandleToken The root layer to capture.
+     * @param consumer The {@link Surface} to capture the layer into.
+     * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
+     *                 Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its
+     *                 native portrait orientation by default, so this is useful for returning
+     *                 captures that are independent of device orientation.
+     */
+    public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) {
+        nativeCaptureLayers(layerHandleToken, consumer, rotation);
+    }
+
     public static class Transaction implements Closeable {
         public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
                 Transaction.class.getClassLoader(),
@@ -1035,9 +1231,9 @@
             return this;
         }
 
-        public Transaction setRelativeLayer(SurfaceControl sc, IBinder relativeTo, int z) {
+        public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
             nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
-                    relativeTo, z);
+                    relativeTo.getHandle(), z);
             return this;
         }
 
diff --git a/android/view/SurfaceView.java b/android/view/SurfaceView.java
index ebb2af4..4eab496 100644
--- a/android/view/SurfaceView.java
+++ b/android/view/SurfaceView.java
@@ -16,115 +16,1215 @@
 
 package android.view;
 
-import com.android.layoutlib.bridge.MockView;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
 
 import android.content.Context;
+import android.content.res.CompatibilityInfo.Translator;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.SystemClock;
 import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.view.SurfaceCallbackHelper;
+
+import java.util.ArrayList;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
- * Mock version of the SurfaceView.
- * Only non override public methods from the real SurfaceView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
+ * Provides a dedicated drawing surface embedded inside of a view hierarchy.
+ * You can control the format of this surface and, if you like, its size; the
+ * SurfaceView takes care of placing the surface at the correct location on the
+ * screen
  *
- * TODO: generate automatically.
+ * <p>The surface is Z ordered so that it is behind the window holding its
+ * SurfaceView; the SurfaceView punches a hole in its window to allow its
+ * surface to be displayed. The view hierarchy will take care of correctly
+ * compositing with the Surface any siblings of the SurfaceView that would
+ * normally appear on top of it. This can be used to place overlays such as
+ * buttons on top of the Surface, though note however that it can have an
+ * impact on performance since a full alpha-blended composite will be performed
+ * each time the Surface changes.
  *
+ * <p> The transparent region that makes the surface visible is based on the
+ * layout positions in the view hierarchy. If the post-layout transform
+ * properties are used to draw a sibling view on top of the SurfaceView, the
+ * view may not be properly composited with the surface.
+ *
+ * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
+ * which can be retrieved by calling {@link #getHolder}.
+ *
+ * <p>The Surface will be created for you while the SurfaceView's window is
+ * visible; you should implement {@link SurfaceHolder.Callback#surfaceCreated}
+ * and {@link SurfaceHolder.Callback#surfaceDestroyed} to discover when the
+ * Surface is created and destroyed as the window is shown and hidden.
+ *
+ * <p>One of the purposes of this class is to provide a surface in which a
+ * secondary thread can render into the screen. If you are going to use it
+ * this way, you need to be aware of some threading semantics:
+ *
+ * <ul>
+ * <li> All SurfaceView and
+ * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
+ * from the thread running the SurfaceView's window (typically the main thread
+ * of the application). They thus need to correctly synchronize with any
+ * state that is also touched by the drawing thread.
+ * <li> You must ensure that the drawing thread only touches the underlying
+ * Surface while it is valid -- between
+ * {@link SurfaceHolder.Callback#surfaceCreated SurfaceHolder.Callback.surfaceCreated()}
+ * and
+ * {@link SurfaceHolder.Callback#surfaceDestroyed SurfaceHolder.Callback.surfaceDestroyed()}.
+ * </ul>
+ *
+ * <p class="note"><strong>Note:</strong> Starting in platform version
+ * {@link android.os.Build.VERSION_CODES#N}, SurfaceView's window position is
+ * updated synchronously with other View rendering. This means that translating
+ * and scaling a SurfaceView on screen will not cause rendering artifacts. Such
+ * artifacts may occur on previous versions of the platform when its window is
+ * positioned asynchronously.</p>
  */
-public class SurfaceView extends MockView {
+public class SurfaceView extends View implements ViewRootImpl.WindowStoppedCallback {
+    private static final String TAG = "SurfaceView";
+    private static final boolean DEBUG = false;
+
+    final ArrayList<SurfaceHolder.Callback> mCallbacks
+            = new ArrayList<SurfaceHolder.Callback>();
+
+    final int[] mLocation = new int[2];
+
+    final ReentrantLock mSurfaceLock = new ReentrantLock();
+    final Surface mSurface = new Surface();       // Current surface in use
+    boolean mDrawingStopped = true;
+    // We use this to track if the application has produced a frame
+    // in to the Surface. Up until that point, we should be careful not to punch
+    // holes.
+    boolean mDrawFinished = false;
+
+    final Rect mScreenRect = new Rect();
+    SurfaceSession mSurfaceSession;
+
+    SurfaceControl mSurfaceControl;
+    // In the case of format changes we switch out the surface in-place
+    // we need to preserve the old one until the new one has drawn.
+    SurfaceControl mDeferredDestroySurfaceControl;
+    final Rect mTmpRect = new Rect();
+    final Configuration mConfiguration = new Configuration();
+
+    int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+
+    boolean mIsCreating = false;
+    private volatile boolean mRtHandlingPositionUpdates = false;
+
+    private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
+            = new ViewTreeObserver.OnScrollChangedListener() {
+                    @Override
+                    public void onScrollChanged() {
+                        updateSurface();
+                    }
+            };
+
+    private final ViewTreeObserver.OnPreDrawListener mDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    // reposition ourselves where the surface is
+                    mHaveFrame = getWidth() > 0 && getHeight() > 0;
+                    updateSurface();
+                    return true;
+                }
+            };
+
+    boolean mRequestedVisible = false;
+    boolean mWindowVisibility = false;
+    boolean mLastWindowVisibility = false;
+    boolean mViewVisibility = false;
+    boolean mWindowStopped = false;
+
+    int mRequestedWidth = -1;
+    int mRequestedHeight = -1;
+    /* Set SurfaceView's format to 565 by default to maintain backward
+     * compatibility with applications assuming this format.
+     */
+    int mRequestedFormat = PixelFormat.RGB_565;
+
+    boolean mHaveFrame = false;
+    boolean mSurfaceCreated = false;
+    long mLastLockTime = 0;
+
+    boolean mVisible = false;
+    int mWindowSpaceLeft = -1;
+    int mWindowSpaceTop = -1;
+    int mSurfaceWidth = -1;
+    int mSurfaceHeight = -1;
+    int mFormat = -1;
+    final Rect mSurfaceFrame = new Rect();
+    int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
+    private Translator mTranslator;
+
+    private boolean mGlobalListenersAdded;
+    private boolean mAttachedToWindow;
+
+    private int mSurfaceFlags = SurfaceControl.HIDDEN;
+
+    private int mPendingReportDraws;
 
     public SurfaceView(Context context) {
         this(context, null);
     }
 
     public SurfaceView(Context context, AttributeSet attrs) {
-        this(context, attrs , 0);
+        this(context, attrs, 0);
     }
 
-    public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
+    public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
     }
 
     public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mRenderNode.requestPositionUpdates(this);
+
+        setWillNotDraw(true);
     }
 
-    public boolean gatherTransparentRegion(Region region) {
-      return false;
-    }
-
-    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
-    }
-
-    public void setZOrderOnTop(boolean onTop) {
-    }
-
-    public void setSecure(boolean isSecure) {
-    }
-
+    /**
+     * Return the SurfaceHolder providing access and control over this
+     * SurfaceView's underlying surface.
+     *
+     * @return SurfaceHolder The holder of the surface.
+     */
     public SurfaceHolder getHolder() {
         return mSurfaceHolder;
     }
 
-    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+    private void updateRequestedVisibility() {
+        mRequestedVisible = mViewVisibility && mWindowVisibility && !mWindowStopped;
+    }
+
+    /** @hide */
+    @Override
+    public void windowStopped(boolean stopped) {
+        mWindowStopped = stopped;
+        updateRequestedVisibility();
+        updateSurface();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        getViewRootImpl().addWindowStoppedCallback(this);
+        mWindowStopped = false;
+
+        mViewVisibility = getVisibility() == VISIBLE;
+        updateRequestedVisibility();
+
+        mAttachedToWindow = true;
+        mParent.requestTransparentRegion(SurfaceView.this);
+        if (!mGlobalListenersAdded) {
+            ViewTreeObserver observer = getViewTreeObserver();
+            observer.addOnScrollChangedListener(mScrollChangedListener);
+            observer.addOnPreDrawListener(mDrawListener);
+            mGlobalListenersAdded = true;
+        }
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        mWindowVisibility = visibility == VISIBLE;
+        updateRequestedVisibility();
+        updateSurface();
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        mViewVisibility = visibility == VISIBLE;
+        boolean newRequestedVisible = mWindowVisibility && mViewVisibility && !mWindowStopped;
+        if (newRequestedVisible != mRequestedVisible) {
+            // our base class (View) invalidates the layout only when
+            // we go from/to the GONE state. However, SurfaceView needs
+            // to request a re-layout when the visibility changes at all.
+            // This is needed because the transparent region is computed
+            // as part of the layout phase, and it changes (obviously) when
+            // the visibility changes.
+            requestLayout();
+        }
+        mRequestedVisible = newRequestedVisible;
+        updateSurface();
+    }
+
+    private void performDrawFinished() {
+        if (mPendingReportDraws > 0) {
+            mDrawFinished = true;
+            if (mAttachedToWindow) {
+                notifyDrawFinished();
+                invalidate();
+            }
+        } else {
+            Log.e(TAG, System.identityHashCode(this) + "finished drawing"
+                    + " but no pending report draw (extra call"
+                    + " to draw completion runnable?)");
+        }
+    }
+
+    void notifyDrawFinished() {
+        ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot != null) {
+            viewRoot.pendingDrawFinished();
+        }
+        mPendingReportDraws--;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        ViewRootImpl viewRoot = getViewRootImpl();
+        // It's possible to create a SurfaceView using the default constructor and never
+        // attach it to a view hierarchy, this is a common use case when dealing with
+        // OpenGL. A developer will probably create a new GLSurfaceView, and let it manage
+        // the lifecycle. Instead of attaching it to a view, he/she can just pass
+        // the SurfaceHolder forward, most live wallpapers do it.
+        if (viewRoot != null) {
+            viewRoot.removeWindowStoppedCallback(this);
+        }
+
+        mAttachedToWindow = false;
+        if (mGlobalListenersAdded) {
+            ViewTreeObserver observer = getViewTreeObserver();
+            observer.removeOnScrollChangedListener(mScrollChangedListener);
+            observer.removeOnPreDrawListener(mDrawListener);
+            mGlobalListenersAdded = false;
+        }
+
+        while (mPendingReportDraws > 0) {
+            notifyDrawFinished();
+        }
+
+        mRequestedVisible = false;
+
+        updateSurface();
+        if (mSurfaceControl != null) {
+            mSurfaceControl.destroy();
+        }
+        mSurfaceControl = null;
+
+        mHaveFrame = false;
+
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = mRequestedWidth >= 0
+                ? resolveSizeAndState(mRequestedWidth, widthMeasureSpec, 0)
+                : getDefaultSize(0, widthMeasureSpec);
+        int height = mRequestedHeight >= 0
+                ? resolveSizeAndState(mRequestedHeight, heightMeasureSpec, 0)
+                : getDefaultSize(0, heightMeasureSpec);
+        setMeasuredDimension(width, height);
+    }
+
+    /** @hide */
+    @Override
+    protected boolean setFrame(int left, int top, int right, int bottom) {
+        boolean result = super.setFrame(left, top, right, bottom);
+        updateSurface();
+        return result;
+    }
+
+    @Override
+    public boolean gatherTransparentRegion(Region region) {
+        if (isAboveParent() || !mDrawFinished) {
+            return super.gatherTransparentRegion(region);
+        }
+
+        boolean opaque = true;
+        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+            // this view draws, remove it from the transparent region
+            opaque = super.gatherTransparentRegion(region);
+        } else if (region != null) {
+            int w = getWidth();
+            int h = getHeight();
+            if (w>0 && h>0) {
+                getLocationInWindow(mLocation);
+                // otherwise, punch a hole in the whole hierarchy
+                int l = mLocation[0];
+                int t = mLocation[1];
+                region.op(l, t, l+w, t+h, Region.Op.UNION);
+            }
+        }
+        if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
+            opaque = false;
+        }
+        return opaque;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mDrawFinished && !isAboveParent()) {
+            // draw() is not called when SKIP_DRAW is set
+            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
+                // punch a whole in the view-hierarchy below us
+                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+            }
+        }
+        super.draw(canvas);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        if (mDrawFinished && !isAboveParent()) {
+            // draw() is not called when SKIP_DRAW is set
+            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+                // punch a whole in the view-hierarchy below us
+                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+            }
+        }
+        super.dispatchDraw(canvas);
+    }
+
+    /**
+     * Control whether the surface view's surface is placed on top of another
+     * regular surface view in the window (but still behind the window itself).
+     * This is typically used to place overlays on top of an underlying media
+     * surface view.
+     *
+     * <p>Note that this must be set before the surface view's containing
+     * window is attached to the window manager.
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+     */
+    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+        mSubLayer = isMediaOverlay
+            ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
+    }
+
+    /**
+     * Control whether the surface view's surface is placed on top of its
+     * window.  Normally it is placed behind the window, to allow it to
+     * (for the most part) appear to composite with the views in the
+     * hierarchy.  By setting this, you cause it to be placed above the
+     * window.  This means that none of the contents of the window this
+     * SurfaceView is in will be visible on top of its surface.
+     *
+     * <p>Note that this must be set before the surface view's containing
+     * window is attached to the window manager.
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+     */
+    public void setZOrderOnTop(boolean onTop) {
+        if (onTop) {
+            mSubLayer = APPLICATION_PANEL_SUBLAYER;
+        } else {
+            mSubLayer = APPLICATION_MEDIA_SUBLAYER;
+        }
+    }
+
+    /**
+     * Control whether the surface view's content should be treated as secure,
+     * preventing it from appearing in screenshots or from being viewed on
+     * non-secure displays.
+     *
+     * <p>Note that this must be set before the surface view's containing
+     * window is attached to the window manager.
+     *
+     * <p>See {@link android.view.Display#FLAG_SECURE} for details.
+     *
+     * @param isSecure True if the surface view is secure.
+     */
+    public void setSecure(boolean isSecure) {
+        if (isSecure) {
+            mSurfaceFlags |= SurfaceControl.SECURE;
+        } else {
+            mSurfaceFlags &= ~SurfaceControl.SECURE;
+        }
+    }
+
+    private void updateOpaqueFlag() {
+        if (!PixelFormat.formatHasAlpha(mRequestedFormat)) {
+            mSurfaceFlags |= SurfaceControl.OPAQUE;
+        } else {
+            mSurfaceFlags &= ~SurfaceControl.OPAQUE;
+        }
+    }
+
+    private Rect getParentSurfaceInsets() {
+        final ViewRootImpl root = getViewRootImpl();
+        if (root == null) {
+            return null;
+        } else {
+            return root.mWindowAttributes.surfaceInsets;
+        }
+    }
+
+    /** @hide */
+    protected void updateSurface() {
+        if (!mHaveFrame) {
+            return;
+        }
+        ViewRootImpl viewRoot = getViewRootImpl();
+        if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+            return;
+        }
+
+        mTranslator = viewRoot.mTranslator;
+        if (mTranslator != null) {
+            mSurface.setCompatibilityTranslator(mTranslator);
+        }
+
+        int myWidth = mRequestedWidth;
+        if (myWidth <= 0) myWidth = getWidth();
+        int myHeight = mRequestedHeight;
+        if (myHeight <= 0) myHeight = getHeight();
+
+        final boolean formatChanged = mFormat != mRequestedFormat;
+        final boolean visibleChanged = mVisible != mRequestedVisible;
+        final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
+                && mRequestedVisible;
+        final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
+        final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
+        boolean redrawNeeded = false;
+
+        if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
+            getLocationInWindow(mLocation);
+
+            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                    + "Changes: creating=" + creating
+                    + " format=" + formatChanged + " size=" + sizeChanged
+                    + " visible=" + visibleChanged
+                    + " left=" + (mWindowSpaceLeft != mLocation[0])
+                    + " top=" + (mWindowSpaceTop != mLocation[1]));
+
+            try {
+                final boolean visible = mVisible = mRequestedVisible;
+                mWindowSpaceLeft = mLocation[0];
+                mWindowSpaceTop = mLocation[1];
+                mSurfaceWidth = myWidth;
+                mSurfaceHeight = myHeight;
+                mFormat = mRequestedFormat;
+                mLastWindowVisibility = mWindowVisibility;
+
+                mScreenRect.left = mWindowSpaceLeft;
+                mScreenRect.top = mWindowSpaceTop;
+                mScreenRect.right = mWindowSpaceLeft + getWidth();
+                mScreenRect.bottom = mWindowSpaceTop + getHeight();
+                if (mTranslator != null) {
+                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+                }
+
+                final Rect surfaceInsets = getParentSurfaceInsets();
+                mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+
+                if (creating) {
+                    mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
+                    mDeferredDestroySurfaceControl = mSurfaceControl;
+
+                    updateOpaqueFlag();
+                    final String name = "SurfaceView - " + viewRoot.getTitle().toString();
+
+                    mSurfaceControl = new SurfaceControlWithBackground(
+                            name,
+                            (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
+                            new SurfaceControl.Builder(mSurfaceSession)
+                                    .setSize(mSurfaceWidth, mSurfaceHeight)
+                                    .setFormat(mFormat)
+                                    .setFlags(mSurfaceFlags));
+                } else if (mSurfaceControl == null) {
+                    return;
+                }
+
+                boolean realSizeChanged = false;
+
+                mSurfaceLock.lock();
+                try {
+                    mDrawingStopped = !visible;
+
+                    if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                            + "Cur surface: " + mSurface);
+
+                    SurfaceControl.openTransaction();
+                    try {
+                        mSurfaceControl.setLayer(mSubLayer);
+                        if (mViewVisibility) {
+                            mSurfaceControl.show();
+                        } else {
+                            mSurfaceControl.hide();
+                        }
+
+                        // While creating the surface, we will set it's initial
+                        // geometry. Outside of that though, we should generally
+                        // leave it to the RenderThread.
+                        //
+                        // There is one more case when the buffer size changes we aren't yet
+                        // prepared to sync (as even following the transaction applying
+                        // we still need to latch a buffer).
+                        // b/28866173
+                        if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
+                            mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
+                            mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
+                                    0.0f, 0.0f,
+                                    mScreenRect.height() / (float) mSurfaceHeight);
+                        }
+                        if (sizeChanged) {
+                            mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
+                        }
+                    } finally {
+                        SurfaceControl.closeTransaction();
+                    }
+
+                    if (sizeChanged || creating) {
+                        redrawNeeded = true;
+                    }
+
+                    mSurfaceFrame.left = 0;
+                    mSurfaceFrame.top = 0;
+                    if (mTranslator == null) {
+                        mSurfaceFrame.right = mSurfaceWidth;
+                        mSurfaceFrame.bottom = mSurfaceHeight;
+                    } else {
+                        float appInvertedScale = mTranslator.applicationInvertedScale;
+                        mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+                        mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
+                    }
+
+                    final int surfaceWidth = mSurfaceFrame.right;
+                    final int surfaceHeight = mSurfaceFrame.bottom;
+                    realSizeChanged = mLastSurfaceWidth != surfaceWidth
+                            || mLastSurfaceHeight != surfaceHeight;
+                    mLastSurfaceWidth = surfaceWidth;
+                    mLastSurfaceHeight = surfaceHeight;
+                } finally {
+                    mSurfaceLock.unlock();
+                }
+
+                try {
+                    redrawNeeded |= visible && !mDrawFinished;
+
+                    SurfaceHolder.Callback callbacks[] = null;
+
+                    final boolean surfaceChanged = creating;
+                    if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
+                        mSurfaceCreated = false;
+                        if (mSurface.isValid()) {
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "visibleChanged -- surfaceDestroyed");
+                            callbacks = getSurfaceCallbacks();
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceDestroyed(mSurfaceHolder);
+                            }
+                            // Since Android N the same surface may be reused and given to us
+                            // again by the system server at a later point. However
+                            // as we didn't do this in previous releases, clients weren't
+                            // necessarily required to clean up properly in
+                            // surfaceDestroyed. This leads to problems for example when
+                            // clients don't destroy their EGL context, and try
+                            // and create a new one on the same surface following reuse.
+                            // Since there is no valid use of the surface in-between
+                            // surfaceDestroyed and surfaceCreated, we force a disconnect,
+                            // so the next connect will always work if we end up reusing
+                            // the surface.
+                            if (mSurface.isValid()) {
+                                mSurface.forceScopedDisconnect();
+                            }
+                        }
+                    }
+
+                    if (creating) {
+                        mSurface.copyFrom(mSurfaceControl);
+                    }
+
+                    if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
+                            < Build.VERSION_CODES.O) {
+                        // Some legacy applications use the underlying native {@link Surface} object
+                        // as a key to whether anything has changed. In these cases, updates to the
+                        // existing {@link Surface} will be ignored when the size changes.
+                        // Therefore, we must explicitly recreate the {@link Surface} in these
+                        // cases.
+                        mSurface.createFrom(mSurfaceControl);
+                    }
+
+                    if (visible && mSurface.isValid()) {
+                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+                            mSurfaceCreated = true;
+                            mIsCreating = true;
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "visibleChanged -- surfaceCreated");
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceCreated(mSurfaceHolder);
+                            }
+                        }
+                        if (creating || formatChanged || sizeChanged
+                                || visibleChanged || realSizeChanged) {
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "surfaceChanged -- format=" + mFormat
+                                    + " w=" + myWidth + " h=" + myHeight);
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+                            for (SurfaceHolder.Callback c : callbacks) {
+                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+                            }
+                        }
+                        if (redrawNeeded) {
+                            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+                                    + "surfaceRedrawNeeded");
+                            if (callbacks == null) {
+                                callbacks = getSurfaceCallbacks();
+                            }
+
+                            mPendingReportDraws++;
+                            viewRoot.drawPending();
+                            SurfaceCallbackHelper sch =
+                                    new SurfaceCallbackHelper(this::onDrawFinished);
+                            sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
+                        }
+                    }
+                } finally {
+                    mIsCreating = false;
+                    if (mSurfaceControl != null && !mSurfaceCreated) {
+                        mSurface.release();
+                        // If we are not in the stopped state, then the destruction of the Surface
+                        // represents a visual change we need to display, and we should go ahead
+                        // and destroy the SurfaceControl. However if we are in the stopped state,
+                        // we can just leave the Surface around so it can be a part of animations,
+                        // and we let the life-time be tied to the parent surface.
+                        if (!mWindowStopped) {
+                            mSurfaceControl.destroy();
+                            mSurfaceControl = null;
+                        }
+                    }
+                }
+            } catch (Exception ex) {
+                Log.e(TAG, "Exception configuring surface", ex);
+            }
+            if (DEBUG) Log.v(
+                TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
+                + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
+                + ", frame=" + mSurfaceFrame);
+        } else {
+            // Calculate the window position in case RT loses the window
+            // and we need to fallback to a UI-thread driven position update
+            getLocationInSurface(mLocation);
+            final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
+                    || mWindowSpaceTop != mLocation[1];
+            final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
+                    || getHeight() != mScreenRect.height();
+            if (positionChanged || layoutSizeChanged) { // Only the position has changed
+                mWindowSpaceLeft = mLocation[0];
+                mWindowSpaceTop = mLocation[1];
+                // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
+                // in view local space.
+                mLocation[0] = getWidth();
+                mLocation[1] = getHeight();
+
+                mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
+                        mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);
+
+                if (mTranslator != null) {
+                    mTranslator.translateRectInAppWindowToScreen(mScreenRect);
+                }
+
+                if (mSurfaceControl == null) {
+                    return;
+                }
+
+                if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
+                    try {
+                        if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
+                                "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+                                mScreenRect.left, mScreenRect.top,
+                                mScreenRect.right, mScreenRect.bottom));
+                        setParentSpaceRectangle(mScreenRect, -1);
+                    } catch (Exception ex) {
+                        Log.e(TAG, "Exception configuring surface", ex);
+                    }
+                }
+            }
+        }
+    }
+
+    private void onDrawFinished() {
+        if (DEBUG) {
+            Log.i(TAG, System.identityHashCode(this) + " "
+                    + "finishedDrawing");
+        }
+
+        if (mDeferredDestroySurfaceControl != null) {
+            mDeferredDestroySurfaceControl.destroy();
+            mDeferredDestroySurfaceControl = null;
+        }
+
+        runOnUiThread(() -> {
+            performDrawFinished();
+        });
+    }
+
+    private void setParentSpaceRectangle(Rect position, long frameNumber) {
+        ViewRootImpl viewRoot = getViewRootImpl();
+
+        SurfaceControl.openTransaction();
+        try {
+            if (frameNumber > 0) {
+                mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
+            }
+            mSurfaceControl.setPosition(position.left, position.top);
+            mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
+                    0.0f, 0.0f,
+                    position.height() / (float) mSurfaceHeight);
+        } finally {
+            SurfaceControl.closeTransaction();
+        }
+    }
+
+    private Rect mRTLastReportedPosition = new Rect();
+
+    /**
+     * Called by native by a Rendering Worker thread to update the window position
+     * @hide
+     */
+    public final void updateSurfacePosition_renderWorker(long frameNumber,
+            int left, int top, int right, int bottom) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+
+        // TODO: This is teensy bit racey in that a brand new SurfaceView moving on
+        // its 2nd frame if RenderThread is running slowly could potentially see
+        // this as false, enter the branch, get pre-empted, then this comes along
+        // and reports a new position, then the UI thread resumes and reports
+        // its position. This could therefore be de-sync'd in that interval, but
+        // the synchronization would violate the rule that RT must never block
+        // on the UI thread which would open up potential deadlocks. The risk of
+        // a single-frame desync is therefore preferable for now.
+        mRtHandlingPositionUpdates = true;
+        if (mRTLastReportedPosition.left == left
+                && mRTLastReportedPosition.top == top
+                && mRTLastReportedPosition.right == right
+                && mRTLastReportedPosition.bottom == bottom) {
+            return;
+        }
+        try {
+            if (DEBUG) {
+                Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
+                        "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+                        frameNumber, left, top, right, bottom));
+            }
+            mRTLastReportedPosition.set(left, top, right, bottom);
+            setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+            // Now overwrite mRTLastReportedPosition with our values
+        } catch (Exception ex) {
+            Log.e(TAG, "Exception from repositionChild", ex);
+        }
+    }
+
+    /**
+     * Called by native on RenderThread to notify that the view is no longer in the
+     * draw tree. UI thread is blocked at this point.
+     * @hide
+     */
+    public final void surfacePositionLost_uiRtSync(long frameNumber) {
+        if (DEBUG) {
+            Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
+                    System.identityHashCode(this), frameNumber));
+        }
+        mRTLastReportedPosition.setEmpty();
+
+        if (mSurfaceControl == null) {
+            return;
+        }
+        if (mRtHandlingPositionUpdates) {
+            mRtHandlingPositionUpdates = false;
+            // This callback will happen while the UI thread is blocked, so we can
+            // safely access other member variables at this time.
+            // So do what the UI thread would have done if RT wasn't handling position
+            // updates.
+            if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
+                try {
+                    if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
+                            "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
+                            mScreenRect.left, mScreenRect.top,
+                            mScreenRect.right, mScreenRect.bottom));
+                    setParentSpaceRectangle(mScreenRect, frameNumber);
+                } catch (Exception ex) {
+                    Log.e(TAG, "Exception configuring surface", ex);
+                }
+            }
+        }
+    }
+
+    private SurfaceHolder.Callback[] getSurfaceCallbacks() {
+        SurfaceHolder.Callback callbacks[];
+        synchronized (mCallbacks) {
+            callbacks = new SurfaceHolder.Callback[mCallbacks.size()];
+            mCallbacks.toArray(callbacks);
+        }
+        return callbacks;
+    }
+
+    /**
+     * This method still exists only for compatibility reasons because some applications have relied
+     * on this method via reflection. See Issue 36345857 for details.
+     *
+     * @deprecated No platform code is using this method anymore.
+     * @hide
+     */
+    @Deprecated
+    public void setWindowType(int type) {
+        if (getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
+            throw new UnsupportedOperationException(
+                    "SurfaceView#setWindowType() has never been a public API.");
+        }
+
+        if (type == TYPE_APPLICATION_PANEL) {
+            Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) "
+                    + "just to make the SurfaceView to be placed on top of its window, you must "
+                    + "call setZOrderOnTop(true) instead.", new Throwable());
+            setZOrderOnTop(true);
+            return;
+        }
+        Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. "
+                + "type=" + type, new Throwable());
+    }
+
+    private void runOnUiThread(Runnable runnable) {
+        Handler handler = getHandler();
+        if (handler != null && handler.getLooper() != Looper.myLooper()) {
+            handler.post(runnable);
+        } else {
+            runnable.run();
+        }
+    }
+
+    /**
+     * Check to see if the surface has fixed size dimensions or if the surface's
+     * dimensions are dimensions are dependent on its current layout.
+     *
+     * @return true if the surface has dimensions that are fixed in size
+     * @hide
+     */
+    public boolean isFixedSize() {
+        return (mRequestedWidth != -1 || mRequestedHeight != -1);
+    }
+
+    private boolean isAboveParent() {
+        return mSubLayer >= 0;
+    }
+
+    private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
+        private static final String LOG_TAG = "SurfaceHolder";
 
         @Override
         public boolean isCreating() {
-            return false;
+            return mIsCreating;
         }
 
         @Override
         public void addCallback(Callback callback) {
+            synchronized (mCallbacks) {
+                // This is a linear search, but in practice we'll
+                // have only a couple callbacks, so it doesn't matter.
+                if (mCallbacks.contains(callback) == false) {
+                    mCallbacks.add(callback);
+                }
+            }
         }
 
         @Override
         public void removeCallback(Callback callback) {
+            synchronized (mCallbacks) {
+                mCallbacks.remove(callback);
+            }
         }
 
         @Override
         public void setFixedSize(int width, int height) {
+            if (mRequestedWidth != width || mRequestedHeight != height) {
+                mRequestedWidth = width;
+                mRequestedHeight = height;
+                requestLayout();
+            }
         }
 
         @Override
         public void setSizeFromLayout() {
+            if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+                mRequestedWidth = mRequestedHeight = -1;
+                requestLayout();
+            }
         }
 
         @Override
         public void setFormat(int format) {
+            // for backward compatibility reason, OPAQUE always
+            // means 565 for SurfaceView
+            if (format == PixelFormat.OPAQUE)
+                format = PixelFormat.RGB_565;
+
+            mRequestedFormat = format;
+            if (mSurfaceControl != null) {
+                updateSurface();
+            }
         }
 
+        /**
+         * @deprecated setType is now ignored.
+         */
         @Override
-        public void setType(int type) {
-        }
+        @Deprecated
+        public void setType(int type) { }
 
         @Override
         public void setKeepScreenOn(boolean screenOn) {
+            runOnUiThread(() -> SurfaceView.this.setKeepScreenOn(screenOn));
         }
 
+        /**
+         * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+         *
+         * After drawing into the provided {@link Canvas}, the caller must
+         * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+         *
+         * The caller must redraw the entire surface.
+         * @return A canvas for drawing into the surface.
+         */
         @Override
         public Canvas lockCanvas() {
-            return null;
+            return internalLockCanvas(null, false);
+        }
+
+        /**
+         * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+         *
+         * After drawing into the provided {@link Canvas}, the caller must
+         * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+         *
+         * @param inOutDirty A rectangle that represents the dirty region that the caller wants
+         * to redraw.  This function may choose to expand the dirty rectangle if for example
+         * the surface has been resized or if the previous contents of the surface were
+         * not available.  The caller must redraw the entire dirty region as represented
+         * by the contents of the inOutDirty rectangle upon return from this function.
+         * The caller may also pass <code>null</code> instead, in the case where the
+         * entire surface should be redrawn.
+         * @return A canvas for drawing into the surface.
+         */
+        @Override
+        public Canvas lockCanvas(Rect inOutDirty) {
+            return internalLockCanvas(inOutDirty, false);
         }
 
         @Override
-        public Canvas lockCanvas(Rect dirty) {
+        public Canvas lockHardwareCanvas() {
+            return internalLockCanvas(null, true);
+        }
+
+        private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
+            mSurfaceLock.lock();
+
+            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
+                    + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
+
+            Canvas c = null;
+            if (!mDrawingStopped && mSurfaceControl != null) {
+                try {
+                    if (hardware) {
+                        c = mSurface.lockHardwareCanvas();
+                    } else {
+                        c = mSurface.lockCanvas(dirty);
+                    }
+                } catch (Exception e) {
+                    Log.e(LOG_TAG, "Exception locking surface", e);
+                }
+            }
+
+            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
+            if (c != null) {
+                mLastLockTime = SystemClock.uptimeMillis();
+                return c;
+            }
+
+            // If the Surface is not ready to be drawn, then return null,
+            // but throttle calls to this function so it isn't called more
+            // than every 100ms.
+            long now = SystemClock.uptimeMillis();
+            long nextTime = mLastLockTime + 100;
+            if (nextTime > now) {
+                try {
+                    Thread.sleep(nextTime-now);
+                } catch (InterruptedException e) {
+                }
+                now = SystemClock.uptimeMillis();
+            }
+            mLastLockTime = now;
+            mSurfaceLock.unlock();
+
             return null;
         }
 
+        /**
+         * Posts the new contents of the {@link Canvas} to the surface and
+         * releases the {@link Canvas}.
+         *
+         * @param canvas The canvas previously obtained from {@link #lockCanvas}.
+         */
         @Override
         public void unlockCanvasAndPost(Canvas canvas) {
+            mSurface.unlockCanvasAndPost(canvas);
+            mSurfaceLock.unlock();
         }
 
         @Override
         public Surface getSurface() {
-            return null;
+            return mSurface;
         }
 
         @Override
         public Rect getSurfaceFrame() {
-            return null;
+            return mSurfaceFrame;
         }
     };
-}
 
+    class SurfaceControlWithBackground extends SurfaceControl {
+        private SurfaceControl mBackgroundControl;
+        private boolean mOpaque = true;
+        public boolean mVisible = false;
+
+        public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
+                       throws Exception {
+            super(b.setName(name).build());
+
+            mBackgroundControl = b.setName("Background for -" + name)
+                    .setFormat(OPAQUE)
+                    .setColorLayer(true)
+                    .build();
+            mOpaque = opaque;
+        }
+
+        @Override
+        public void setAlpha(float alpha) {
+            super.setAlpha(alpha);
+            mBackgroundControl.setAlpha(alpha);
+        }
+
+        @Override
+        public void setLayer(int zorder) {
+            super.setLayer(zorder);
+            // -3 is below all other child layers as SurfaceView never goes below -2
+            mBackgroundControl.setLayer(-3);
+        }
+
+        @Override
+        public void setPosition(float x, float y) {
+            super.setPosition(x, y);
+            mBackgroundControl.setPosition(x, y);
+        }
+
+        @Override
+        public void setSize(int w, int h) {
+            super.setSize(w, h);
+            mBackgroundControl.setSize(w, h);
+        }
+
+        @Override
+        public void setWindowCrop(Rect crop) {
+            super.setWindowCrop(crop);
+            mBackgroundControl.setWindowCrop(crop);
+        }
+
+        @Override
+        public void setFinalCrop(Rect crop) {
+            super.setFinalCrop(crop);
+            mBackgroundControl.setFinalCrop(crop);
+        }
+
+        @Override
+        public void setLayerStack(int layerStack) {
+            super.setLayerStack(layerStack);
+            mBackgroundControl.setLayerStack(layerStack);
+        }
+
+        @Override
+        public void setOpaque(boolean isOpaque) {
+            super.setOpaque(isOpaque);
+            mOpaque = isOpaque;
+            updateBackgroundVisibility();
+        }
+
+        @Override
+        public void setSecure(boolean isSecure) {
+            super.setSecure(isSecure);
+        }
+
+        @Override
+        public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+            super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+            mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy);
+        }
+
+        @Override
+        public void hide() {
+            super.hide();
+            mVisible = false;
+            updateBackgroundVisibility();
+        }
+
+        @Override
+        public void show() {
+            super.show();
+            mVisible = true;
+            updateBackgroundVisibility();
+        }
+
+        @Override
+        public void destroy() {
+            super.destroy();
+            mBackgroundControl.destroy();
+         }
+
+        @Override
+        public void release() {
+            super.release();
+            mBackgroundControl.release();
+        }
+
+        @Override
+        public void setTransparentRegionHint(Region region) {
+            super.setTransparentRegionHint(region);
+            mBackgroundControl.setTransparentRegionHint(region);
+        }
+
+        @Override
+        public void deferTransactionUntil(IBinder handle, long frame) {
+            super.deferTransactionUntil(handle, frame);
+            mBackgroundControl.deferTransactionUntil(handle, frame);
+        }
+
+        @Override
+        public void deferTransactionUntil(Surface barrier, long frame) {
+            super.deferTransactionUntil(barrier, frame);
+            mBackgroundControl.deferTransactionUntil(barrier, frame);
+        }
+
+        void updateBackgroundVisibility() {
+            if (mOpaque && mVisible) {
+                mBackgroundControl.show();
+            } else {
+                mBackgroundControl.hide();
+            }
+        }
+    }
+}
diff --git a/android/view/TouchDelegate.java b/android/view/TouchDelegate.java
index cf36f43..dc50fa1 100644
--- a/android/view/TouchDelegate.java
+++ b/android/view/TouchDelegate.java
@@ -44,7 +44,7 @@
 
     /**
      * mBounds inflated to include some slop. This rect is to track whether the motion events
-     * should be considered to be be within the delegate view.
+     * should be considered to be within the delegate view.
      */
     private Rect mSlopBounds;
 
@@ -64,14 +64,12 @@
     public static final int BELOW = 2;
 
     /**
-     * The touchable region of the View extends to the left of its
-     * actual extent.
+     * The touchable region of the View extends to the left of its actual extent.
      */
     public static final int TO_LEFT = 4;
 
     /**
-     * The touchable region of the View extends to the right of its
-     * actual extent.
+     * The touchable region of the View extends to the right of its actual extent.
      */
     public static final int TO_RIGHT = 8;
 
@@ -108,28 +106,24 @@
         boolean handled = false;
 
         switch (event.getAction()) {
-        case MotionEvent.ACTION_DOWN:
-            Rect bounds = mBounds;
-
-            if (bounds.contains(x, y)) {
-                mDelegateTargeted = true;
-                sendToDelegate = true;
-            }
-            break;
-        case MotionEvent.ACTION_UP:
-        case MotionEvent.ACTION_MOVE:
-            sendToDelegate = mDelegateTargeted;
-            if (sendToDelegate) {
-                Rect slopBounds = mSlopBounds;
-                if (!slopBounds.contains(x, y)) {
-                    hit = false;
+            case MotionEvent.ACTION_DOWN:
+                mDelegateTargeted = mBounds.contains(x, y);
+                sendToDelegate = mDelegateTargeted;
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_MOVE:
+                sendToDelegate = mDelegateTargeted;
+                if (sendToDelegate) {
+                    Rect slopBounds = mSlopBounds;
+                    if (!slopBounds.contains(x, y)) {
+                        hit = false;
+                    }
                 }
-            }
-            break;
-        case MotionEvent.ACTION_CANCEL:
-            sendToDelegate = mDelegateTargeted;
-            mDelegateTargeted = false;
-            break;
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                sendToDelegate = mDelegateTargeted;
+                mDelegateTargeted = false;
+                break;
         }
         if (sendToDelegate) {
             final View delegateView = mDelegateView;
diff --git a/android/view/ViewGroup_Delegate.java b/android/view/ViewGroup_Delegate.java
index 4b760a7..6daae20 100644
--- a/android/view/ViewGroup_Delegate.java
+++ b/android/view/ViewGroup_Delegate.java
@@ -67,12 +67,12 @@
             Outline outline) {
         float elevation = getElevation(child, parent);
         if(outline.mMode == Outline.MODE_ROUND_RECT && outline.mRect != null) {
-            RectShadowPainter.paintShadow(outline, elevation, canvas);
+            RectShadowPainter.paintShadow(outline, elevation, canvas, child.getAlpha());
             return;
         }
         BufferedImage shadow = null;
         if (outline.mPath != null) {
-            shadow = getPathShadow(outline, canvas, elevation);
+            shadow = getPathShadow(outline, canvas, elevation, child.getAlpha());
         }
         if (shadow == null) {
             return;
@@ -91,7 +91,8 @@
         return child.getZ() - parent.getZ();
     }
 
-    private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation) {
+    private static BufferedImage getPathShadow(Outline outline, Canvas canvas, float elevation,
+            float alpha) {
         Rect clipBounds = canvas.getClipBounds();
         if (clipBounds.isEmpty()) {
           return null;
@@ -101,7 +102,7 @@
         Graphics2D graphics = image.createGraphics();
         graphics.draw(Path_Delegate.getDelegate(outline.mPath.mNativePath).getJavaShape());
         graphics.dispose();
-        return ShadowPainter.createDropShadow(image, (int) elevation);
+        return ShadowPainter.createDropShadow(image, (int) elevation, alpha);
     }
 
     // Copied from android.view.View#draw(Canvas, ViewGroup, long) and removed code paths
diff --git a/android/view/ViewRootImpl.java b/android/view/ViewRootImpl.java
index 99438d8..37829f0 100644
--- a/android/view/ViewRootImpl.java
+++ b/android/view/ViewRootImpl.java
@@ -2284,18 +2284,36 @@
             }
         }
 
-        if (mFirst && sAlwaysAssignFocus) {
-            // handle first focus request
-            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
-                    + mView.hasFocus());
-            if (mView != null) {
-                if (!mView.hasFocus()) {
-                    mView.restoreDefaultFocus();
-                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
-                            + mView.findFocus());
-                } else {
-                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
-                            + mView.findFocus());
+        if (mFirst) {
+            if (sAlwaysAssignFocus) {
+                // handle first focus request
+                if (DEBUG_INPUT_RESIZE) {
+                    Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
+                }
+                if (mView != null) {
+                    if (!mView.hasFocus()) {
+                        mView.restoreDefaultFocus();
+                        if (DEBUG_INPUT_RESIZE) {
+                            Log.v(mTag, "First: requested focused view=" + mView.findFocus());
+                        }
+                    } else {
+                        if (DEBUG_INPUT_RESIZE) {
+                            Log.v(mTag, "First: existing focused view=" + mView.findFocus());
+                        }
+                    }
+                }
+            } else {
+                // Some views (like ScrollView) won't hand focus to descendants that aren't within
+                // their viewport. Before layout, there's a good change these views are size 0
+                // which means no children can get focus. After layout, this view now has size, but
+                // is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
+                // case where the child has a size prior to layout and thus won't trigger
+                // focusableViewAvailable).
+                View focused = mView.findFocus();
+                if (focused instanceof ViewGroup
+                        && ((ViewGroup) focused).getDescendantFocusability()
+                                == ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+                    focused.restoreDefaultFocus();
                 }
             }
         }
diff --git a/android/view/accessibility/AccessibilityManager.java b/android/view/accessibility/AccessibilityManager.java
index 11cb046..0b9bc57 100644
--- a/android/view/accessibility/AccessibilityManager.java
+++ b/android/view/accessibility/AccessibilityManager.java
@@ -16,46 +16,152 @@
 
 package android.view.accessibility;
 
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_ENABLE_ACCESSIBILITY_VOLUME;
+
+import android.Manifest;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemService;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
 import android.view.IWindow;
 import android.view.View;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IntPair;
+
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 /**
- * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
- * Such events are generated when something notable happens in the user interface,
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s,
+ * and provides facilities for querying the accessibility state of the system.
+ * Accessibility events are generated when something notable happens in the user interface,
  * for example an {@link android.app.Activity} starts, the focus or selection of a
  * {@link android.view.View} changes etc. Parties interested in handling accessibility
  * events implement and register an accessibility service which extends
- * {@code android.accessibilityservice.AccessibilityService}.
+ * {@link android.accessibilityservice.AccessibilityService}.
  *
  * @see AccessibilityEvent
- * @see android.content.Context#getSystemService
+ * @see AccessibilityNodeInfo
+ * @see android.accessibilityservice.AccessibilityService
+ * @see Context#getSystemService
+ * @see Context#ACCESSIBILITY_SERVICE
  */
-@SuppressWarnings("UnusedDeclaration")
+@SystemService(Context.ACCESSIBILITY_SERVICE)
 public final class AccessibilityManager {
+    private static final boolean DEBUG = false;
 
-    private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0);
+    private static final String LOG_TAG = "AccessibilityManager";
 
+    /** @hide */
+    public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
+
+    /** @hide */
+    public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
+
+    /** @hide */
+    public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004;
+
+    /** @hide */
+    public static final int DALTONIZER_DISABLED = -1;
+
+    /** @hide */
+    public static final int DALTONIZER_SIMULATE_MONOCHROMACY = 0;
+
+    /** @hide */
+    public static final int DALTONIZER_CORRECT_DEUTERANOMALY = 12;
+
+    /** @hide */
+    public static final int AUTOCLICK_DELAY_DEFAULT = 600;
 
     /**
-     * Listener for the accessibility state.
+     * Activity action: Launch UI to manage which accessibility service or feature is assigned
+     * to the navigation bar Accessibility button.
+     * <p>
+     * Input: Nothing.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
+            "com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
+
+    static final Object sInstanceSync = new Object();
+
+    private static AccessibilityManager sInstance;
+
+    private final Object mLock = new Object();
+
+    private IAccessibilityManager mService;
+
+    final int mUserId;
+
+    final Handler mHandler;
+
+    final Handler.Callback mCallback;
+
+    boolean mIsEnabled;
+
+    int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+
+    boolean mIsTouchExplorationEnabled;
+
+    boolean mIsHighTextContrastEnabled;
+
+    private final ArrayMap<AccessibilityStateChangeListener, Handler>
+            mAccessibilityStateChangeListeners = new ArrayMap<>();
+
+    private final ArrayMap<TouchExplorationStateChangeListener, Handler>
+            mTouchExplorationStateChangeListeners = new ArrayMap<>();
+
+    private final ArrayMap<HighTextContrastChangeListener, Handler>
+            mHighTextContrastStateChangeListeners = new ArrayMap<>();
+
+    private final ArrayMap<AccessibilityServicesStateChangeListener, Handler>
+            mServicesStateChangeListeners = new ArrayMap<>();
+
+    /**
+     * Map from a view's accessibility id to the list of request preparers set for that view
+     */
+    private SparseArray<List<AccessibilityRequestPreparer>> mRequestPreparerLists;
+
+    /**
+     * Listener for the system accessibility state. To listen for changes to the
+     * accessibility state on the device, implement this interface and register
+     * it with the system by calling {@link #addAccessibilityStateChangeListener}.
      */
     public interface AccessibilityStateChangeListener {
 
         /**
-         * Called back on change in the accessibility state.
+         * Called when the accessibility enabled state changes.
          *
          * @param enabled Whether accessibility is enabled.
          */
-        public void onAccessibilityStateChanged(boolean enabled);
+        void onAccessibilityStateChanged(boolean enabled);
     }
 
     /**
@@ -71,7 +177,24 @@
          *
          * @param enabled Whether touch exploration is enabled.
          */
-        public void onTouchExplorationStateChanged(boolean enabled);
+        void onTouchExplorationStateChanged(boolean enabled);
+    }
+
+    /**
+     * Listener for changes to the state of accessibility services. Changes include services being
+     * enabled or disabled, or changes to the {@link AccessibilityServiceInfo} of a running service.
+     * {@see #addAccessibilityServicesStateChangeListener}.
+     *
+     * @hide
+     */
+    public interface AccessibilityServicesStateChangeListener {
+
+        /**
+         * Called when the state of accessibility services changes.
+         *
+         * @param manager The manager that is calling back
+         */
+        void onAccessibilityServicesStateChanged(AccessibilityManager manager);
     }
 
     /**
@@ -79,6 +202,8 @@
      * the high text contrast state on the device, implement this interface and
      * register it with the system by calling
      * {@link #addHighTextContrastStateChangeListener}.
+     *
+     * @hide
      */
     public interface HighTextContrastChangeListener {
 
@@ -87,26 +212,72 @@
          *
          * @param enabled Whether high text contrast is enabled.
          */
-        public void onHighTextContrastStateChanged(boolean enabled);
+        void onHighTextContrastStateChanged(boolean enabled);
     }
 
     private final IAccessibilityManagerClient.Stub mClient =
             new IAccessibilityManagerClient.Stub() {
-                public void setState(int state) {
-                }
+        @Override
+        public void setState(int state) {
+            // We do not want to change this immediately as the application may
+            // have already checked that accessibility is on and fired an event,
+            // that is now propagating up the view tree, Hence, if accessibility
+            // is now off an exception will be thrown. We want to have the exception
+            // enforcement to guard against apps that fire unnecessary accessibility
+            // events when accessibility is off.
+            mHandler.obtainMessage(MyCallback.MSG_SET_STATE, state, 0).sendToTarget();
+        }
 
-                public void notifyServicesStateChanged() {
+        @Override
+        public void notifyServicesStateChanged() {
+            final ArrayMap<AccessibilityServicesStateChangeListener, Handler> listeners;
+            synchronized (mLock) {
+                if (mServicesStateChangeListeners.isEmpty()) {
+                    return;
                 }
+                listeners = new ArrayMap<>(mServicesStateChangeListeners);
+            }
 
-                public void setRelevantEventTypes(int eventTypes) {
-                }
-            };
+            int numListeners = listeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                final AccessibilityServicesStateChangeListener listener =
+                        mServicesStateChangeListeners.keyAt(i);
+                mServicesStateChangeListeners.valueAt(i).post(() -> listener
+                        .onAccessibilityServicesStateChanged(AccessibilityManager.this));
+            }
+        }
+
+        @Override
+        public void setRelevantEventTypes(int eventTypes) {
+            mRelevantEventTypes = eventTypes;
+        }
+    };
 
     /**
      * Get an AccessibilityManager instance (create one if necessary).
      *
+     * @param context Context in which this manager operates.
+     *
+     * @hide
      */
     public static AccessibilityManager getInstance(Context context) {
+        synchronized (sInstanceSync) {
+            if (sInstance == null) {
+                final int userId;
+                if (Binder.getCallingUid() == Process.SYSTEM_UID
+                        || context.checkCallingOrSelfPermission(
+                                Manifest.permission.INTERACT_ACROSS_USERS)
+                                        == PackageManager.PERMISSION_GRANTED
+                        || context.checkCallingOrSelfPermission(
+                                Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                                        == PackageManager.PERMISSION_GRANTED) {
+                    userId = UserHandle.USER_CURRENT;
+                } else {
+                    userId = UserHandle.myUserId();
+                }
+                sInstance = new AccessibilityManager(context, null, userId);
+            }
+        }
         return sInstance;
     }
 
@@ -114,21 +285,68 @@
      * Create an instance.
      *
      * @param context A {@link Context}.
+     * @param service An interface to the backing service.
+     * @param userId User id under which to run.
+     *
+     * @hide
      */
     public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
+        // Constructor can't be chained because we can't create an instance of an inner class
+        // before calling another constructor.
+        mCallback = new MyCallback();
+        mHandler = new Handler(context.getMainLooper(), mCallback);
+        mUserId = userId;
+        synchronized (mLock) {
+            tryConnectToServiceLocked(service);
+        }
     }
 
+    /**
+     * Create an instance.
+     *
+     * @param handler The handler to use
+     * @param service An interface to the backing service.
+     * @param userId User id under which to run.
+     *
+     * @hide
+     */
+    public AccessibilityManager(Handler handler, IAccessibilityManager service, int userId) {
+        mCallback = new MyCallback();
+        mHandler = handler;
+        mUserId = userId;
+        synchronized (mLock) {
+            tryConnectToServiceLocked(service);
+        }
+    }
+
+    /**
+     * @hide
+     */
     public IAccessibilityManagerClient getClient() {
         return mClient;
     }
 
     /**
-     * Returns if the {@link AccessibilityManager} is enabled.
+     * @hide
+     */
+    @VisibleForTesting
+    public Handler.Callback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Returns if the accessibility in the system is enabled.
      *
-     * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+     * @return True if accessibility is enabled, false otherwise.
      */
     public boolean isEnabled() {
-        return false;
+        synchronized (mLock) {
+            IAccessibilityManager service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+            return mIsEnabled;
+        }
     }
 
     /**
@@ -137,7 +355,13 @@
      * @return True if touch exploration is enabled, false otherwise.
      */
     public boolean isTouchExplorationEnabled() {
-        return true;
+        synchronized (mLock) {
+            IAccessibilityManager service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+            return mIsTouchExplorationEnabled;
+        }
     }
 
     /**
@@ -147,35 +371,169 @@
      * doing its own rendering and does not rely on the platform rendering pipeline.
      * </p>
      *
+     * @return True if high text contrast is enabled, false otherwise.
+     *
+     * @hide
      */
     public boolean isHighTextContrastEnabled() {
-        return false;
+        synchronized (mLock) {
+            IAccessibilityManager service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+            return mIsHighTextContrastEnabled;
+        }
     }
 
     /**
      * Sends an {@link AccessibilityEvent}.
+     *
+     * @param event The event to send.
+     *
+     * @throws IllegalStateException if accessibility is not enabled.
+     *
+     * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
+     * events is through calling
+     * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+     * instead of this method to allow predecessors to augment/filter events sent by
+     * their descendants.
      */
     public void sendAccessibilityEvent(AccessibilityEvent event) {
+        final IAccessibilityManager service;
+        final int userId;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+            if (!mIsEnabled) {
+                Looper myLooper = Looper.myLooper();
+                if (myLooper == Looper.getMainLooper()) {
+                    throw new IllegalStateException(
+                            "Accessibility off. Did you forget to check that?");
+                } else {
+                    // If we're not running on the thread with the main looper, it's possible for
+                    // the state of accessibility to change between checking isEnabled and
+                    // calling this method. So just log the error rather than throwing the
+                    // exception.
+                    Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
+                    return;
+                }
+            }
+            if ((event.getEventType() & mRelevantEventTypes) == 0) {
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event
+                            + " that is not among "
+                            + AccessibilityEvent.eventTypeToString(mRelevantEventTypes));
+                }
+                return;
+            }
+            userId = mUserId;
+        }
+        try {
+            event.setEventTime(SystemClock.uptimeMillis());
+            // it is possible that this manager is in the same process as the service but
+            // client using it is called through Binder from another process. Example: MMS
+            // app adds a SMS notification and the NotificationManagerService calls this method
+            long identityToken = Binder.clearCallingIdentity();
+            service.sendAccessibilityEvent(event, userId);
+            Binder.restoreCallingIdentity(identityToken);
+            if (DEBUG) {
+                Log.i(LOG_TAG, event + " sent");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error during sending " + event + " ", re);
+        } finally {
+            event.recycle();
+        }
     }
 
     /**
-     * Requests interruption of the accessibility feedback from all accessibility services.
+     * Requests feedback interruption from all accessibility services.
      */
     public void interrupt() {
+        final IAccessibilityManager service;
+        final int userId;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+            if (!mIsEnabled) {
+                Looper myLooper = Looper.myLooper();
+                if (myLooper == Looper.getMainLooper()) {
+                    throw new IllegalStateException(
+                            "Accessibility off. Did you forget to check that?");
+                } else {
+                    // If we're not running on the thread with the main looper, it's possible for
+                    // the state of accessibility to change between checking isEnabled and
+                    // calling this method. So just log the error rather than throwing the
+                    // exception.
+                    Log.e(LOG_TAG, "Interrupt called with accessibility disabled");
+                    return;
+                }
+            }
+            userId = mUserId;
+        }
+        try {
+            service.interrupt(userId);
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Requested interrupt from all services");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
+        }
     }
 
     /**
      * Returns the {@link ServiceInfo}s of the installed accessibility services.
      *
      * @return An unmodifiable list with {@link ServiceInfo}s.
+     *
+     * @deprecated Use {@link #getInstalledAccessibilityServiceList()}
      */
     @Deprecated
     public List<ServiceInfo> getAccessibilityServiceList() {
-        return Collections.emptyList();
+        List<AccessibilityServiceInfo> infos = getInstalledAccessibilityServiceList();
+        List<ServiceInfo> services = new ArrayList<>();
+        final int infoCount = infos.size();
+        for (int i = 0; i < infoCount; i++) {
+            AccessibilityServiceInfo info = infos.get(i);
+            services.add(info.getResolveInfo().serviceInfo);
+        }
+        return Collections.unmodifiableList(services);
     }
 
+    /**
+     * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
+     *
+     * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
+     */
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
-        return Collections.emptyList();
+        final IAccessibilityManager service;
+        final int userId;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return Collections.emptyList();
+            }
+            userId = mUserId;
+        }
+
+        List<AccessibilityServiceInfo> services = null;
+        try {
+            services = service.getInstalledAccessibilityServiceList(userId);
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+        }
+        if (services != null) {
+            return Collections.unmodifiableList(services);
+        } else {
+            return Collections.emptyList();
+        }
     }
 
     /**
@@ -190,21 +548,48 @@
      * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
      * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
      * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
+     * @see AccessibilityServiceInfo#FEEDBACK_BRAILLE
      */
     public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
             int feedbackTypeFlags) {
-        return Collections.emptyList();
+        final IAccessibilityManager service;
+        final int userId;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return Collections.emptyList();
+            }
+            userId = mUserId;
+        }
+
+        List<AccessibilityServiceInfo> services = null;
+        try {
+            services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
+            if (DEBUG) {
+                Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+        }
+        if (services != null) {
+            return Collections.unmodifiableList(services);
+        } else {
+            return Collections.emptyList();
+        }
     }
 
     /**
      * Registers an {@link AccessibilityStateChangeListener} for changes in
-     * the global accessibility state of the system.
+     * the global accessibility state of the system. Equivalent to calling
+     * {@link #addAccessibilityStateChangeListener(AccessibilityStateChangeListener, Handler)}
+     * with a null handler.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
+     * @return Always returns {@code true}.
      */
     public boolean addAccessibilityStateChangeListener(
-            AccessibilityStateChangeListener listener) {
+            @NonNull AccessibilityStateChangeListener listener) {
+        addAccessibilityStateChangeListener(listener, null);
         return true;
     }
 
@@ -218,22 +603,40 @@
      *                for a callback on the process's main handler.
      */
     public void addAccessibilityStateChangeListener(
-            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {}
+            @NonNull AccessibilityStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mAccessibilityStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
 
+    /**
+     * Unregisters an {@link AccessibilityStateChangeListener}.
+     *
+     * @param listener The listener.
+     * @return True if the listener was previously registered.
+     */
     public boolean removeAccessibilityStateChangeListener(
-            AccessibilityStateChangeListener listener) {
-        return true;
+            @NonNull AccessibilityStateChangeListener listener) {
+        synchronized (mLock) {
+            int index = mAccessibilityStateChangeListeners.indexOfKey(listener);
+            mAccessibilityStateChangeListeners.remove(listener);
+            return (index >= 0);
+        }
     }
 
     /**
      * Registers a {@link TouchExplorationStateChangeListener} for changes in
-     * the global touch exploration state of the system.
+     * the global touch exploration state of the system. Equivalent to calling
+     * {@link #addTouchExplorationStateChangeListener(TouchExplorationStateChangeListener, Handler)}
+     * with a null handler.
      *
      * @param listener The listener.
-     * @return True if successfully registered.
+     * @return Always returns {@code true}.
      */
     public boolean addTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
+        addTouchExplorationStateChangeListener(listener, null);
         return true;
     }
 
@@ -247,17 +650,103 @@
      *                for a callback on the process's main handler.
      */
     public void addTouchExplorationStateChangeListener(
-            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {}
+            @NonNull TouchExplorationStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mTouchExplorationStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
 
     /**
      * Unregisters a {@link TouchExplorationStateChangeListener}.
      *
      * @param listener The listener.
-     * @return True if successfully unregistered.
+     * @return True if listener was previously registered.
      */
     public boolean removeTouchExplorationStateChangeListener(
             @NonNull TouchExplorationStateChangeListener listener) {
-        return true;
+        synchronized (mLock) {
+            int index = mTouchExplorationStateChangeListeners.indexOfKey(listener);
+            mTouchExplorationStateChangeListeners.remove(listener);
+            return (index >= 0);
+        }
+    }
+
+    /**
+     * Registers a {@link AccessibilityServicesStateChangeListener}.
+     *
+     * @param listener The listener.
+     * @param handler The handler on which the listener should be called back, or {@code null}
+     *                for a callback on the process's main handler.
+     * @hide
+     */
+    public void addAccessibilityServicesStateChangeListener(
+            @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mServicesStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
+
+    /**
+     * Unregisters a {@link AccessibilityServicesStateChangeListener}.
+     *
+     * @param listener The listener.
+     *
+     * @hide
+     */
+    public void removeAccessibilityServicesStateChangeListener(
+            @NonNull AccessibilityServicesStateChangeListener listener) {
+        // Final CopyOnWriteArrayList - no lock needed.
+        mServicesStateChangeListeners.remove(listener);
+    }
+
+    /**
+     * Registers a {@link AccessibilityRequestPreparer}.
+     */
+    public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+        if (mRequestPreparerLists == null) {
+            mRequestPreparerLists = new SparseArray<>(1);
+        }
+        int id = preparer.getView().getAccessibilityViewId();
+        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(id);
+        if (requestPreparerList == null) {
+            requestPreparerList = new ArrayList<>(1);
+            mRequestPreparerLists.put(id, requestPreparerList);
+        }
+        requestPreparerList.add(preparer);
+    }
+
+    /**
+     * Unregisters a {@link AccessibilityRequestPreparer}.
+     */
+    public void removeAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
+        if (mRequestPreparerLists == null) {
+            return;
+        }
+        int viewId = preparer.getView().getAccessibilityViewId();
+        List<AccessibilityRequestPreparer> requestPreparerList = mRequestPreparerLists.get(viewId);
+        if (requestPreparerList != null) {
+            requestPreparerList.remove(preparer);
+            if (requestPreparerList.isEmpty()) {
+                mRequestPreparerLists.remove(viewId);
+            }
+        }
+    }
+
+    /**
+     * Get the preparers that are registered for an accessibility ID
+     *
+     * @param id The ID of interest
+     * @return The list of preparers, or {@code null} if there are none.
+     *
+     * @hide
+     */
+    public List<AccessibilityRequestPreparer> getRequestPreparersForAccessibilityId(int id) {
+        if (mRequestPreparerLists == null) {
+            return null;
+        }
+        return mRequestPreparerLists.get(id);
     }
 
     /**
@@ -269,7 +758,12 @@
      * @hide
      */
     public void addHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {}
+            @NonNull HighTextContrastChangeListener listener, @Nullable Handler handler) {
+        synchronized (mLock) {
+            mHighTextContrastStateChangeListeners
+                    .put(listener, (handler == null) ? mHandler : handler);
+        }
+    }
 
     /**
      * Unregisters a {@link HighTextContrastChangeListener}.
@@ -279,7 +773,51 @@
      * @hide
      */
     public void removeHighTextContrastStateChangeListener(
-            @NonNull HighTextContrastChangeListener listener) {}
+            @NonNull HighTextContrastChangeListener listener) {
+        synchronized (mLock) {
+            mHighTextContrastStateChangeListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Check if the accessibility volume stream is active.
+     *
+     * @return True if accessibility volume is active (i.e. some service has requested it). False
+     * otherwise.
+     * @hide
+     */
+    public boolean isAccessibilityVolumeStreamActive() {
+        List<AccessibilityServiceInfo> serviceInfos =
+                getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+        for (int i = 0; i < serviceInfos.size(); i++) {
+            if ((serviceInfos.get(i).flags & FLAG_ENABLE_ACCESSIBILITY_VOLUME) != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Report a fingerprint gesture to accessibility. Only available for the system process.
+     *
+     * @param keyCode The key code of the gesture
+     * @return {@code true} if accessibility consumes the event. {@code false} if not.
+     * @hide
+     */
+    public boolean sendFingerprintGesture(int keyCode) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+        }
+        try {
+            return service.sendFingerprintGesture(keyCode);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
 
     /**
      * Sets the current state and notifies listeners, if necessary.
@@ -287,14 +825,314 @@
      * @param stateFlags The state flags.
      */
     private void setStateLocked(int stateFlags) {
+        final boolean enabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
+        final boolean touchExplorationEnabled =
+                (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
+        final boolean highTextContrastEnabled =
+                (stateFlags & STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED) != 0;
+
+        final boolean wasEnabled = mIsEnabled;
+        final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
+        final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
+
+        // Ensure listeners get current state from isZzzEnabled() calls.
+        mIsEnabled = enabled;
+        mIsTouchExplorationEnabled = touchExplorationEnabled;
+        mIsHighTextContrastEnabled = highTextContrastEnabled;
+
+        if (wasEnabled != enabled) {
+            notifyAccessibilityStateChanged();
+        }
+
+        if (wasTouchExplorationEnabled != touchExplorationEnabled) {
+            notifyTouchExplorationStateChanged();
+        }
+
+        if (wasHighTextContrastEnabled != highTextContrastEnabled) {
+            notifyHighTextContrastStateChanged();
+        }
     }
 
+    /**
+     * Find an installed service with the specified {@link ComponentName}.
+     *
+     * @param componentName The name to match to the service.
+     *
+     * @return The info corresponding to the installed service, or {@code null} if no such service
+     * is installed.
+     * @hide
+     */
+    public AccessibilityServiceInfo getInstalledServiceInfoWithComponentName(
+            ComponentName componentName) {
+        final List<AccessibilityServiceInfo> installedServiceInfos =
+                getInstalledAccessibilityServiceList();
+        if ((installedServiceInfos == null) || (componentName == null)) {
+            return null;
+        }
+        for (int i = 0; i < installedServiceInfos.size(); i++) {
+            if (componentName.equals(installedServiceInfos.get(i).getComponentName())) {
+                return installedServiceInfos.get(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds an accessibility interaction connection interface for a given window.
+     * @param windowToken The window token to which a connection is added.
+     * @param connection The connection.
+     *
+     * @hide
+     */
     public int addAccessibilityInteractionConnection(IWindow windowToken,
             IAccessibilityInteractionConnection connection) {
+        final IAccessibilityManager service;
+        final int userId;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return View.NO_ID;
+            }
+            userId = mUserId;
+        }
+        try {
+            return service.addAccessibilityInteractionConnection(windowToken, connection, userId);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
+        }
         return View.NO_ID;
     }
 
+    /**
+     * Removed an accessibility interaction connection interface for a given window.
+     * @param windowToken The window token to which a connection is removed.
+     *
+     * @hide
+     */
     public void removeAccessibilityInteractionConnection(IWindow windowToken) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.removeAccessibilityInteractionConnection(windowToken);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re);
+        }
     }
 
+    /**
+     * Perform the accessibility shortcut if the caller has permission.
+     *
+     * @hide
+     */
+    public void performAccessibilityShortcut() {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.performAccessibilityShortcut();
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
+        }
+    }
+
+    /**
+     * Notifies that the accessibility button in the system's navigation area has been clicked
+     *
+     * @hide
+     */
+    public void notifyAccessibilityButtonClicked() {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.notifyAccessibilityButtonClicked();
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
+        }
+    }
+
+    /**
+     * Notifies that the visibility of the accessibility button in the system's navigation area
+     * has changed.
+     *
+     * @param shown {@code true} if the accessibility button is visible within the system
+     *                  navigation area, {@code false} otherwise
+     * @hide
+     */
+    public void notifyAccessibilityButtonVisibilityChanged(boolean shown) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.notifyAccessibilityButtonVisibilityChanged(shown);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error while dispatching accessibility button visibility change", re);
+        }
+    }
+
+    /**
+     * Set an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
+     * window. Intended for use by the System UI only.
+     *
+     * @param connection The connection to handle the actions. Set to {@code null} to avoid
+     * affecting the actions.
+     *
+     * @hide
+     */
+    public void setPictureInPictureActionReplacingConnection(
+            @Nullable IAccessibilityInteractionConnection connection) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.setPictureInPictureActionReplacingConnection(connection);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error setting picture in picture action replacement", re);
+        }
+    }
+
+    private IAccessibilityManager getServiceLocked() {
+        if (mService == null) {
+            tryConnectToServiceLocked(null);
+        }
+        return mService;
+    }
+
+    private void tryConnectToServiceLocked(IAccessibilityManager service) {
+        if (service == null) {
+            IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+            if (iBinder == null) {
+                return;
+            }
+            service = IAccessibilityManager.Stub.asInterface(iBinder);
+        }
+
+        try {
+            final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);
+            setStateLocked(IntPair.first(userStateAndRelevantEvents));
+            mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
+            mService = service;
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
+        }
+    }
+
+    /**
+     * Notifies the registered {@link AccessibilityStateChangeListener}s.
+     */
+    private void notifyAccessibilityStateChanged() {
+        final boolean isEnabled;
+        final ArrayMap<AccessibilityStateChangeListener, Handler> listeners;
+        synchronized (mLock) {
+            if (mAccessibilityStateChangeListeners.isEmpty()) {
+                return;
+            }
+            isEnabled = mIsEnabled;
+            listeners = new ArrayMap<>(mAccessibilityStateChangeListeners);
+        }
+
+        int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final AccessibilityStateChangeListener listener =
+                    mAccessibilityStateChangeListeners.keyAt(i);
+            mAccessibilityStateChangeListeners.valueAt(i)
+                    .post(() -> listener.onAccessibilityStateChanged(isEnabled));
+        }
+    }
+
+    /**
+     * Notifies the registered {@link TouchExplorationStateChangeListener}s.
+     */
+    private void notifyTouchExplorationStateChanged() {
+        final boolean isTouchExplorationEnabled;
+        final ArrayMap<TouchExplorationStateChangeListener, Handler> listeners;
+        synchronized (mLock) {
+            if (mTouchExplorationStateChangeListeners.isEmpty()) {
+                return;
+            }
+            isTouchExplorationEnabled = mIsTouchExplorationEnabled;
+            listeners = new ArrayMap<>(mTouchExplorationStateChangeListeners);
+        }
+
+        int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final TouchExplorationStateChangeListener listener =
+                    mTouchExplorationStateChangeListeners.keyAt(i);
+            mTouchExplorationStateChangeListeners.valueAt(i)
+                    .post(() -> listener.onTouchExplorationStateChanged(isTouchExplorationEnabled));
+        }
+    }
+
+    /**
+     * Notifies the registered {@link HighTextContrastChangeListener}s.
+     */
+    private void notifyHighTextContrastStateChanged() {
+        final boolean isHighTextContrastEnabled;
+        final ArrayMap<HighTextContrastChangeListener, Handler> listeners;
+        synchronized (mLock) {
+            if (mHighTextContrastStateChangeListeners.isEmpty()) {
+                return;
+            }
+            isHighTextContrastEnabled = mIsHighTextContrastEnabled;
+            listeners = new ArrayMap<>(mHighTextContrastStateChangeListeners);
+        }
+
+        int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final HighTextContrastChangeListener listener =
+                    mHighTextContrastStateChangeListeners.keyAt(i);
+            mHighTextContrastStateChangeListeners.valueAt(i)
+                    .post(() -> listener.onHighTextContrastStateChanged(isHighTextContrastEnabled));
+        }
+    }
+
+    /**
+     * Determines if the accessibility button within the system navigation area is supported.
+     *
+     * @return {@code true} if the accessibility button is supported on this device,
+     * {@code false} otherwise
+     */
+    public static boolean isAccessibilityButtonSupported() {
+        final Resources res = Resources.getSystem();
+        return res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
+    }
+
+    private final class MyCallback implements Handler.Callback {
+        public static final int MSG_SET_STATE = 1;
+
+        @Override
+        public boolean handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_SET_STATE: {
+                    // See comment at mClient
+                    final int state = message.arg1;
+                    synchronized (mLock) {
+                        setStateLocked(state);
+                    }
+                } break;
+            }
+            return true;
+        }
+    }
 }
diff --git a/android/view/autofill/AutofillManager.java b/android/view/autofill/AutofillManager.java
index e564fa3..e79d201 100644
--- a/android/view/autofill/AutofillManager.java
+++ b/android/view/autofill/AutofillManager.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -224,7 +225,7 @@
 
     /**
      * State where the autofill context was finished by the server because the autofill
-     * service could not autofill the page.
+     * service could not autofill the activity.
      *
      * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
      * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
@@ -242,6 +243,16 @@
     public static final int STATE_SHOWING_SAVE_UI = 3;
 
     /**
+     * State where the autofill is disabled because the service cannot autofill the activity at all.
+     *
+     * <p>In this state, every call is ignored, even {@link #requestAutofill(View)}
+     * (and {@link #requestAutofill(View, int, Rect)}).
+     *
+     * @hide
+     */
+    public static final int STATE_DISABLED_BY_SERVICE = 4;
+
+    /**
      * Makes an authentication id from a request id and a dataset id.
      *
      * @param requestId The request id.
@@ -398,6 +409,11 @@
          * Runs the specified action on the UI thread.
          */
         void runOnUiThread(Runnable action);
+
+        /**
+         * Gets the complete component name of this client.
+         */
+        ComponentName getComponentName();
     }
 
     /**
@@ -506,7 +522,7 @@
      * @return whether autofill is enabled for the current user.
      */
     public boolean isEnabled() {
-        if (!hasAutofillFeature()) {
+        if (!hasAutofillFeature() || isDisabledByService()) {
             return false;
         }
         synchronized (mLock) {
@@ -580,19 +596,31 @@
         notifyViewEntered(view, 0);
     }
 
+    private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
+        if (isDisabledByService()) {
+            if (sVerbose) {
+                Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+                        + ") on state " + getStateAsStringLocked());
+            }
+            return true;
+        }
+        if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) {
+            if (sVerbose) {
+                Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+                        + ") on state " + getStateAsStringLocked());
+            }
+            return true;
+        }
+        return false;
+    }
+
     private void notifyViewEntered(@NonNull View view, int flags) {
         if (!hasAutofillFeature()) {
             return;
         }
         AutofillCallback callback = null;
         synchronized (mLock) {
-            if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
-                if (sVerbose) {
-                    Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
-                            + "): ignored on state " + getStateAsStringLocked());
-                }
-                return;
-            }
+            if (shouldIgnoreViewEnteredLocked(view, flags)) return;
 
             ensureServiceClientAddedIfNeededLocked();
 
@@ -717,14 +745,8 @@
         }
         AutofillCallback callback = null;
         synchronized (mLock) {
-            if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
-                if (sVerbose) {
-                    Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
-                            + ", virtualId=" + virtualId
-                            + "): ignored on state " + getStateAsStringLocked());
-                }
-                return;
-            }
+            if (shouldIgnoreViewEnteredLocked(view, flags)) return;
+
             ensureServiceClientAddedIfNeededLocked();
 
             if (!mEnabled) {
@@ -1059,13 +1081,13 @@
             return;
         }
         try {
+            final AutofillClient client = getClientLocked();
             mSessionId = mService.startSession(mContext.getActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
-                    mCallback != null, flags, mContext.getOpPackageName());
+                    mCallback != null, flags, client.getComponentName());
             if (mSessionId != NO_SESSION) {
                 mState = STATE_ACTIVE;
             }
-            final AutofillClient client = getClientLocked();
             if (client != null) {
                 client.autofillCallbackResetableStateAvailable();
             }
@@ -1120,14 +1142,14 @@
 
         try {
             if (restartIfNecessary) {
+                final AutofillClient client = getClientLocked();
                 final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
                         mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
-                        mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action);
+                        mCallback != null, flags, client.getComponentName(), mSessionId, action);
                 if (newId != mSessionId) {
                     if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
                     mSessionId = newId;
                     mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
-                    final AutofillClient client = getClientLocked();
                     if (client != null) {
                         client.autofillCallbackResetableStateAvailable();
                     }
@@ -1437,7 +1459,9 @@
      * Marks the state of the session as finished.
      *
      * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
-     *  FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed).
+     *  FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or
+     *  {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill
+     *  requests for the activity).
      */
     private void setSessionFinished(int newState) {
         synchronized (mLock) {
@@ -1482,10 +1506,10 @@
         }
     }
 
-    private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+    private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
         if (sVerbose) {
             Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
-                    + ", finished=" + sessionFinished);
+                    + ", sessionFinishedState=" + sessionFinishedState);
         }
         final View anchor = findView(id);
         if (anchor == null) {
@@ -1508,9 +1532,9 @@
             }
         }
 
-        if (sessionFinished) {
+        if (sessionFinishedState != 0) {
             // Callback call was "hijacked" to also update the session state.
-            setSessionFinished(STATE_FINISHED);
+            setSessionFinished(sessionFinishedState);
         }
     }
 
@@ -1613,6 +1637,8 @@
                 return "STATE_FINISHED";
             case STATE_SHOWING_SAVE_UI:
                 return "STATE_SHOWING_SAVE_UI";
+            case STATE_DISABLED_BY_SERVICE:
+                return "STATE_DISABLED_BY_SERVICE";
             default:
                 return "INVALID:" + mState;
         }
@@ -1622,8 +1648,8 @@
         return mState == STATE_ACTIVE;
     }
 
-    private boolean isFinishedLocked() {
-        return mState == STATE_FINISHED;
+    private boolean isDisabledByService() {
+        return mState == STATE_DISABLED_BY_SERVICE;
     }
 
     private void post(Runnable runnable) {
@@ -1957,10 +1983,10 @@
         }
 
         @Override
-        public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+        public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished));
+                afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState));
             }
         }
 
diff --git a/android/view/textclassifier/TextClassification.java b/android/view/textclassifier/TextClassification.java
index 8c3b8a2..26d2141 100644
--- a/android/view/textclassifier/TextClassification.java
+++ b/android/view/textclassifier/TextClassification.java
@@ -33,6 +33,52 @@
 
 /**
  * Information for generating a widget to handle classified text.
+ *
+ * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
+ * be used to build a widget that can be used to act on classified text.
+ *
+ * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
+ *
+ * <pre>{@code
+ *   // Called preferably outside the UiThread.
+ *   TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ *   // Called on the UiThread.
+ *   Button button = new Button(context);
+ *   button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
+ *   button.setText(classification.getLabel());
+ *   button.setOnClickListener(classification.getOnClickListener());
+ * }</pre>
+ *
+ * <p>e.g. starting an action mode with menu items that can handle the classified text:
+ *
+ * <pre>{@code
+ *   // Called preferably outside the UiThread.
+ *   final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ *   // Called on the UiThread.
+ *   view.startActionMode(new ActionMode.Callback() {
+ *
+ *       public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ *           for (int i = 0; i < classification.getActionCount(); i++) {
+ *               if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) {
+ *                   menu.add(Menu.NONE, i, 20, classification.getLabel(i))
+ *                      .setIcon(classification.getIcon(i))
+ *                      .setIntent(classification.getIntent(i));
+ *               }
+ *           }
+ *           return true;
+ *       }
+ *
+ *       public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ *           context.startActivity(item.getIntent());
+ *           return true;
+ *       }
+ *
+ *       ...
+ *   });
+ * }</pre>
+ *
  */
 public final class TextClassification {
 
diff --git a/android/view/textclassifier/TextClassifier.java b/android/view/textclassifier/TextClassifier.java
index c3601d9..46dbd0e 100644
--- a/android/view/textclassifier/TextClassifier.java
+++ b/android/view/textclassifier/TextClassifier.java
@@ -29,8 +29,8 @@
 /**
  * Interface for providing text classification related features.
  *
- * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
- * avoid calling them on the UI thread.
+ * <p>Unless otherwise stated, methods of this interface are blocking operations.
+ * Avoid calling them on the UI thread.
  */
 public interface TextClassifier {
 
@@ -75,8 +75,8 @@
     };
 
     /**
-     * Returns suggested text selection indices, recognized types and their associated confidence
-     * scores. The selections are ordered from highest to lowest scoring.
+     * Returns suggested text selection start and end indices, recognized entity types, and their
+     * associated confidence scores. The entity types are ordered from highest to lowest scoring.
      *
      * @param text text providing context for the selected text (which is specified
      *      by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
diff --git a/android/view/textservice/TextServicesManager.java b/android/view/textservice/TextServicesManager.java
index 8e1f218..f368c74 100644
--- a/android/view/textservice/TextServicesManager.java
+++ b/android/view/textservice/TextServicesManager.java
@@ -1,58 +1,213 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
  */
 
 package android.view.textservice;
 
+import android.annotation.SystemService;
+import android.content.Context;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.util.Log;
 import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
 
+import com.android.internal.textservice.ITextServicesManager;
+
 import java.util.Locale;
 
 /**
- * A stub class of TextServicesManager for Layout-Lib.
+ * System API to the overall text services, which arbitrates interaction between applications
+ * and text services.
+ *
+ * The user can change the current text services in Settings. And also applications can specify
+ * the target text services.
+ *
+ * <h3>Architecture Overview</h3>
+ *
+ * <p>There are three primary parties involved in the text services
+ * framework (TSF) architecture:</p>
+ *
+ * <ul>
+ * <li> The <strong>text services manager</strong> as expressed by this class
+ * is the central point of the system that manages interaction between all
+ * other parts.  It is expressed as the client-side API here which exists
+ * in each application context and communicates with a global system service
+ * that manages the interaction across all processes.
+ * <li> A <strong>text service</strong> implements a particular
+ * interaction model allowing the client application to retrieve information of text.
+ * The system binds to the current text service that is in use, causing it to be created and run.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the text service
+ * manager for connections to text services.
+ * </ul>
+ *
+ * <h3>Text services sessions</h3>
+ * <ul>
+ * <li>The <strong>spell checker session</strong> is one of the text services.
+ * {@link android.view.textservice.SpellCheckerSession}</li>
+ * </ul>
+ *
  */
+@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
 public final class TextServicesManager {
-    private static final TextServicesManager sInstance = new TextServicesManager();
-    private static final SpellCheckerInfo[] EMPTY_SPELL_CHECKER_INFO = new SpellCheckerInfo[0];
+    private static final String TAG = TextServicesManager.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private static TextServicesManager sInstance;
+
+    private final ITextServicesManager mService;
+
+    private TextServicesManager() throws ServiceNotFoundException {
+        mService = ITextServicesManager.Stub.asInterface(
+                ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
+    }
 
     /**
      * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
      * @hide
      */
     public static TextServicesManager getInstance() {
-        return sInstance;
+        synchronized (TextServicesManager.class) {
+            if (sInstance == null) {
+                try {
+                    sInstance = new TextServicesManager();
+                } catch (ServiceNotFoundException e) {
+                    throw new IllegalStateException(e);
+                }
+            }
+            return sInstance;
+        }
     }
 
+    /**
+     * Returns the language component of a given locale string.
+     */
+    private static String parseLanguageFromLocaleString(String locale) {
+        final int idx = locale.indexOf('_');
+        if (idx < 0) {
+            return locale;
+        } else {
+            return locale.substring(0, idx);
+        }
+    }
+
+    /**
+     * Get a spell checker session for the specified spell checker
+     * @param locale the locale for the spell checker. If {@code locale} is null and
+     * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
+     * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
+     * the locale specified in Settings will be returned only when it is same as {@code locale}.
+     * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
+     * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
+     * selected.
+     * @param listener a spell checker session lister for getting results from a spell checker.
+     * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
+     * languages in settings will be returned.
+     * @return the spell checker session of the spell checker
+     */
     public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
             SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
-        return null;
+        if (listener == null) {
+            throw new NullPointerException();
+        }
+        if (!referToSpellCheckerLanguageSettings && locale == null) {
+            throw new IllegalArgumentException("Locale should not be null if you don't refer"
+                    + " settings.");
+        }
+
+        if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
+            return null;
+        }
+
+        final SpellCheckerInfo sci;
+        try {
+            sci = mService.getCurrentSpellChecker(null);
+        } catch (RemoteException e) {
+            return null;
+        }
+        if (sci == null) {
+            return null;
+        }
+        SpellCheckerSubtype subtypeInUse = null;
+        if (referToSpellCheckerLanguageSettings) {
+            subtypeInUse = getCurrentSpellCheckerSubtype(true);
+            if (subtypeInUse == null) {
+                return null;
+            }
+            if (locale != null) {
+                final String subtypeLocale = subtypeInUse.getLocale();
+                final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+                if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
+                    return null;
+                }
+            }
+        } else {
+            final String localeStr = locale.toString();
+            for (int i = 0; i < sci.getSubtypeCount(); ++i) {
+                final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
+                final String tempSubtypeLocale = subtype.getLocale();
+                final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
+                if (tempSubtypeLocale.equals(localeStr)) {
+                    subtypeInUse = subtype;
+                    break;
+                } else if (tempSubtypeLanguage.length() >= 2 &&
+                        locale.getLanguage().equals(tempSubtypeLanguage)) {
+                    subtypeInUse = subtype;
+                }
+            }
+        }
+        if (subtypeInUse == null) {
+            return null;
+        }
+        final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
+        try {
+            mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
+                    session.getTextServicesSessionListener(),
+                    session.getSpellCheckerSessionListener(), bundle);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return session;
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo[] getEnabledSpellCheckers() {
-        return EMPTY_SPELL_CHECKER_INFO;
+        try {
+            final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
+            if (DBG) {
+                Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
+            }
+            return retval;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
      * @hide
      */
     public SpellCheckerInfo getCurrentSpellChecker() {
-        return null;
+        try {
+            // Passing null as a locale for ICS
+            return mService.getCurrentSpellChecker(null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -60,13 +215,22 @@
      */
     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
             boolean allowImplicitlySelectedSubtype) {
-        return null;
+        try {
+            // Passing null as a locale until we support multiple enabled spell checker subtypes.
+            return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
      * @hide
      */
     public boolean isSpellCheckerEnabled() {
-        return false;
+        try {
+            return mService.isSpellCheckerEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 }
diff --git a/android/webkit/MimeTypeMap.java b/android/webkit/MimeTypeMap.java
index 39874e8..3861695 100644
--- a/android/webkit/MimeTypeMap.java
+++ b/android/webkit/MimeTypeMap.java
@@ -37,7 +37,7 @@
     }
 
     /**
-     * Returns the file extension or an empty string iff there is no
+     * Returns the file extension or an empty string if there is no
      * extension. This method is a convenience method for obtaining the
      * extension of a url and has undefined results for other Strings.
      * @param url
@@ -76,7 +76,7 @@
     /**
      * Return {@code true} if the given MIME type has an entry in the map.
      * @param mimeType A MIME type (i.e. text/plain)
-     * @return {@code true} iff there is a mimeType entry in the map.
+     * @return {@code true} if there is a mimeType entry in the map.
      */
     public boolean hasMimeType(String mimeType) {
         return MimeUtils.hasMimeType(mimeType);
@@ -85,7 +85,7 @@
     /**
      * Return the MIME type for the given extension.
      * @param extension A file extension without the leading '.'
-     * @return The MIME type for the given extension or {@code null} iff there is none.
+     * @return The MIME type for the given extension or {@code null} if there is none.
      */
     @Nullable
     public String getMimeTypeFromExtension(String extension) {
@@ -100,7 +100,7 @@
     /**
      * Return {@code true} if the given extension has a registered MIME type.
      * @param extension A file extension without the leading '.'
-     * @return {@code true} iff there is an extension entry in the map.
+     * @return {@code true} if there is an extension entry in the map.
      */
     public boolean hasExtension(String extension) {
         return MimeUtils.hasExtension(extension);
@@ -111,7 +111,7 @@
      * MIME types map to multiple extensions. This call will return the most
      * common extension for the given MIME type.
      * @param mimeType A MIME type (i.e. text/plain)
-     * @return The extension for the given MIME type or {@code null} iff there is none.
+     * @return The extension for the given MIME type or {@code null} if there is none.
      */
     @Nullable
     public String getExtensionFromMimeType(String mimeType) {
diff --git a/android/webkit/URLUtil.java b/android/webkit/URLUtil.java
index c2f121a..84c000a 100644
--- a/android/webkit/URLUtil.java
+++ b/android/webkit/URLUtil.java
@@ -137,7 +137,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is correctly URL encoded
+     * @return {@code true} if the url is correctly URL encoded
      */
     static boolean verifyURLEncoding(String url) {
         int count = url.length();
@@ -171,14 +171,14 @@
     }
 
     /**
-     * @return {@code true} iff the url is an asset file.
+     * @return {@code true} if the url is an asset file.
      */
     public static boolean isAssetUrl(String url) {
         return (null != url) && url.startsWith(ASSET_BASE);
     }
 
     /**
-     * @return {@code true} iff the url is a resource file.
+     * @return {@code true} if the url is a resource file.
      * @hide
      */
     public static boolean isResourceUrl(String url) {
@@ -186,7 +186,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is a proxy url to allow cookieless network
+     * @return {@code true} if the url is a proxy url to allow cookieless network
      * requests from a file url.
      * @deprecated Cookieless proxy is no longer supported.
      */
@@ -196,7 +196,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is a local file.
+     * @return {@code true} if the url is a local file.
      */
     public static boolean isFileUrl(String url) {
         return (null != url) && (url.startsWith(FILE_BASE) &&
@@ -205,28 +205,28 @@
     }
 
     /**
-     * @return {@code true} iff the url is an about: url.
+     * @return {@code true} if the url is an about: url.
      */
     public static boolean isAboutUrl(String url) {
         return (null != url) && url.startsWith("about:");
     }
 
     /**
-     * @return {@code true} iff the url is a data: url.
+     * @return {@code true} if the url is a data: url.
      */
     public static boolean isDataUrl(String url) {
         return (null != url) && url.startsWith("data:");
     }
 
     /**
-     * @return {@code true} iff the url is a javascript: url.
+     * @return {@code true} if the url is a javascript: url.
      */
     public static boolean isJavaScriptUrl(String url) {
         return (null != url) && url.startsWith("javascript:");
     }
 
     /**
-     * @return {@code true} iff the url is an http: url.
+     * @return {@code true} if the url is an http: url.
      */
     public static boolean isHttpUrl(String url) {
         return (null != url) &&
@@ -235,7 +235,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is an https: url.
+     * @return {@code true} if the url is an https: url.
      */
     public static boolean isHttpsUrl(String url) {
         return (null != url) &&
@@ -244,7 +244,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is a network url.
+     * @return {@code true} if the url is a network url.
      */
     public static boolean isNetworkUrl(String url) {
         if (url == null || url.length() == 0) {
@@ -254,14 +254,14 @@
     }
 
     /**
-     * @return {@code true} iff the url is a content: url.
+     * @return {@code true} if the url is a content: url.
      */
     public static boolean isContentUrl(String url) {
         return (null != url) && url.startsWith(CONTENT_BASE);
     }
 
     /**
-     * @return {@code true} iff the url is valid.
+     * @return {@code true} if the url is valid.
      */
     public static boolean isValidUrl(String url) {
         if (url == null || url.length() == 0) {
diff --git a/android/webkit/WebView.java b/android/webkit/WebView.java
index 202f204..259bf60 100644
--- a/android/webkit/WebView.java
+++ b/android/webkit/WebView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2006 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,223 +16,3001 @@
 
 package android.webkit;
 
-import com.android.layoutlib.bridge.MockView;
-
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.Widget;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.graphics.Picture;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.http.SslCertificate;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.StrictMode;
+import android.print.PrintDocumentAdapter;
+import android.security.KeyChain;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.DragEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
+import android.view.ViewStructure;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.autofill.AutofillValue;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.textclassifier.TextClassifier;
+import android.widget.AbsoluteLayout;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Map;
 
 /**
- * Mock version of the WebView.
- * Only non override public methods from the real WebView have been added in there.
- * Methods that take an unknown class as parameter or as return object, have been removed for now.
- * 
- * TODO: generate automatically.
+ * <p>A View that displays web pages. This class is the basis upon which you
+ * can roll your own web browser or simply display some online content within your Activity.
+ * It uses the WebKit rendering engine to display
+ * web pages and includes methods to navigate forward and backward
+ * through a history, zoom in and out, perform text searches and more.
+ *
+ * <p>Note that, in order for your Activity to access the Internet and load web pages
+ * in a WebView, you must add the {@code INTERNET} permissions to your
+ * Android Manifest file:
+ *
+ * <pre>
+ * {@code <uses-permission android:name="android.permission.INTERNET" />}
+ * </pre>
+ *
+ * <p>This must be a child of the <a
+ * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a>
+ * element.
+ *
+ * <p>For more information, read
+ * <a href="{@docRoot}guide/webapps/webview.html">Building Web Apps in WebView</a>.
+ *
+ * <h3>Basic usage</h3>
+ *
+ * <p>By default, a WebView provides no browser-like widgets, does not
+ * enable JavaScript and web page errors are ignored. If your goal is only
+ * to display some HTML as a part of your UI, this is probably fine;
+ * the user won't need to interact with the web page beyond reading
+ * it, and the web page won't need to interact with the user. If you
+ * actually want a full-blown web browser, then you probably want to
+ * invoke the Browser application with a URL Intent rather than show it
+ * with a WebView. For example:
+ * <pre>
+ * Uri uri = Uri.parse("https://www.example.com");
+ * Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ * startActivity(intent);
+ * </pre>
+ * <p>See {@link android.content.Intent} for more information.
+ *
+ * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout,
+ * or set the entire Activity window as a WebView during {@link
+ * android.app.Activity#onCreate(Bundle) onCreate()}:
+ *
+ * <pre class="prettyprint">
+ * WebView webview = new WebView(this);
+ * setContentView(webview);
+ * </pre>
+ *
+ * <p>Then load the desired web page:
+ *
+ * <pre>
+ * // Simplest usage: note that an exception will NOT be thrown
+ * // if there is an error loading this page (see below).
+ * webview.loadUrl("https://example.com/");
+ *
+ * // OR, you can also load from an HTML string:
+ * String summary = "&lt;html>&lt;body>You scored &lt;b>192&lt;/b> points.&lt;/body>&lt;/html>";
+ * webview.loadData(summary, "text/html", null);
+ * // ... although note that there are restrictions on what this HTML can do.
+ * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link
+ * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info.
+ * </pre>
+ *
+ * <p>A WebView has several customization points where you can add your
+ * own behavior. These are:
+ *
+ * <ul>
+ *   <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass.
+ *       This class is called when something that might impact a
+ *       browser UI happens, for instance, progress updates and
+ *       JavaScript alerts are sent here (see <a
+ * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>).
+ *   </li>
+ *   <li>Creating and setting a {@link android.webkit.WebViewClient} subclass.
+ *       It will be called when things happen that impact the
+ *       rendering of the content, eg, errors or form submissions. You
+ *       can also intercept URL loading here (via {@link
+ * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String)
+ * shouldOverrideUrlLoading()}).</li>
+ *   <li>Modifying the {@link android.webkit.WebSettings}, such as
+ * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean)
+ * setJavaScriptEnabled()}. </li>
+ *   <li>Injecting Java objects into the WebView using the
+ *       {@link android.webkit.WebView#addJavascriptInterface} method. This
+ *       method allows you to inject Java objects into a page's JavaScript
+ *       context, so that they can be accessed by JavaScript in the page.</li>
+ * </ul>
+ *
+ * <p>Here's a more complicated example, showing error handling,
+ *    settings, and progress notification:
+ *
+ * <pre class="prettyprint">
+ * // Let's display the progress in the activity title bar, like the
+ * // browser app does.
+ * getWindow().requestFeature(Window.FEATURE_PROGRESS);
+ *
+ * webview.getSettings().setJavaScriptEnabled(true);
+ *
+ * final Activity activity = this;
+ * webview.setWebChromeClient(new WebChromeClient() {
+ *   public void onProgressChanged(WebView view, int progress) {
+ *     // Activities and WebViews measure progress with different scales.
+ *     // The progress meter will automatically disappear when we reach 100%
+ *     activity.setProgress(progress * 1000);
+ *   }
+ * });
+ * webview.setWebViewClient(new WebViewClient() {
+ *   public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+ *     Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show();
+ *   }
+ * });
+ *
+ * webview.loadUrl("https://developer.android.com/");
+ * </pre>
+ *
+ * <h3>Zoom</h3>
+ *
+ * <p>To enable the built-in zoom, set
+ * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)}
+ * (introduced in API level {@link android.os.Build.VERSION_CODES#CUPCAKE}).
+ *
+ * <p>NOTE: Using zoom if either the height or width is set to
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} may lead to undefined behavior
+ * and should be avoided.
+ *
+ * <h3>Cookie and window management</h3>
+ *
+ * <p>For obvious security reasons, your application has its own
+ * cache, cookie store etc.&mdash;it does not share the Browser
+ * application's data.
+ *
+ * <p>By default, requests by the HTML to open new windows are
+ * ignored. This is {@code true} whether they be opened by JavaScript or by
+ * the target attribute on a link. You can customize your
+ * {@link WebChromeClient} to provide your own behavior for opening multiple windows,
+ * and render them in whatever manner you want.
+ *
+ * <p>The standard behavior for an Activity is to be destroyed and
+ * recreated when the device orientation or any other configuration changes. This will cause
+ * the WebView to reload the current page. If you don't want that, you
+ * can set your Activity to handle the {@code orientation} and {@code keyboardHidden}
+ * changes, and then just leave the WebView alone. It'll automatically
+ * re-orient itself as appropriate. Read <a
+ * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for
+ * more information about how to handle configuration changes during runtime.
+ *
+ *
+ * <h3>Building web pages to support different screen densities</h3>
+ *
+ * <p>The screen density of a device is based on the screen resolution. A screen with low density
+ * has fewer available pixels per inch, where a screen with high density
+ * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
+ * screen is important because, other things being equal, a UI element (such as a button) whose
+ * height and width are defined in terms of screen pixels will appear larger on the lower density
+ * screen and smaller on the higher density screen.
+ * For simplicity, Android collapses all actual screen densities into three generalized densities:
+ * high, medium, and low.
+ * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
+ * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
+ * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
+ * are bigger).
+ * Starting with API level {@link android.os.Build.VERSION_CODES#ECLAIR}, WebView supports DOM, CSS,
+ * and meta tag features to help you (as a web developer) target screens with different screen
+ * densities.
+ * <p>Here's a summary of the features you can use to handle different screen densities:
+ * <ul>
+ * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the
+ * default scaling factor used for the current device. For example, if the value of {@code
+ * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device
+ * and default scaling is not applied to the web page; if the value is "1.5", then the device is
+ * considered a high density device (hdpi) and the page content is scaled 1.5x; if the
+ * value is "0.75", then the device is considered a low density device (ldpi) and the content is
+ * scaled 0.75x.</li>
+ * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
+ * densities for which this style sheet is to be used. The corresponding value should be either
+ * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
+ * density, or high density screens, respectively. For example:
+ * <pre>
+ * &lt;link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /&gt;</pre>
+ * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
+ * which is the high density pixel ratio.
+ * </li>
+ * </ul>
+ *
+ * <h3>HTML5 Video support</h3>
+ *
+ * <p>In order to support inline HTML5 video in your application you need to have hardware
+ * acceleration turned on.
+ *
+ * <h3>Full screen support</h3>
+ *
+ * <p>In order to support full screen &mdash; for video or other HTML content &mdash; you need to set a
+ * {@link android.webkit.WebChromeClient} and implement both
+ * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
+ * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is
+ * missing then the web contents will not be allowed to enter full screen. Optionally you can implement
+ * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video
+ * is loading.
+ *
+ * <h3>HTML5 Geolocation API support</h3>
+ *
+ * <p>For applications targeting Android N and later releases
+ * (API level > {@link android.os.Build.VERSION_CODES#M}) the geolocation api is only supported on
+ * secure origins such as https. For such applications requests to geolocation api on non-secure
+ * origins are automatically denied without invoking the corresponding
+ * {@link WebChromeClient#onGeolocationPermissionsShowPrompt(String, GeolocationPermissions.Callback)}
+ * method.
+ *
+ * <h3>Layout size</h3>
+ * <p>
+ * It is recommended to set the WebView layout height to a fixed value or to
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
+ * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+ * for the height none of the WebView's parents should use a
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in
+ * incorrect sizing of the views.
+ *
+ * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+ * enables the following behaviors:
+ * <ul>
+ * <li>The HTML body layout height is set to a fixed value. This means that elements with a height
+ * relative to the HTML body may not be sized correctly. </li>
+ * <li>For applications targeting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the
+ * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li>
+ * </ul>
+ *
+ * <p>
+ * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not
+ * supported. If such a width is used the WebView will attempt to use the width of the parent
+ * instead.
+ *
+ * <h3>Metrics</h3>
+ *
+ * <p>
+ * WebView may upload anonymous diagnostic data to Google when the user has consented. This data
+ * helps Google improve WebView. Data is collected on a per-app basis for each app which has
+ * instantiated a WebView. An individual app can opt out of this feature by putting the following
+ * tag in its manifest:
+ * <pre>
+ * &lt;meta-data android:name="android.webkit.WebView.MetricsOptOut"
+ *            android:value="true" /&gt;
+ * </pre>
+ * <p>
+ * Data will only be uploaded for a given app if the user has consented AND the app has not opted
+ * out.
+ *
+ * <h3>Safe Browsing</h3>
+ *
+ * <p>
+ * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
+ * user to allow them to navigate back safely or proceed to the malicious page.
+ * <p>
+ * The recommended way for apps to enable the feature is putting the following tag in the manifest:
+ * <p>
+ * <pre>
+ * &lt;meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
+ *            android:value="true" /&gt;
+ * </pre>
  *
  */
-public class WebView extends MockView {
+// Implementation notes.
+// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
+// class instance. WebView extends {@link AbsoluteLayout} for backward compatibility reasons.
+// Methods are delegated to the provider implementation: all public API methods introduced in this
+// file are fully delegated, whereas public and protected methods from the View base classes are
+// only delegated where a specific need exists for them to do so.
+@Widget
+public class WebView extends AbsoluteLayout
+        implements ViewTreeObserver.OnGlobalFocusChangeListener,
+        ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
+
+    private static final String LOGTAG = "WebView";
+
+    // Throwing an exception for incorrect thread usage if the
+    // build target is JB MR2 or newer. Defaults to false, and is
+    // set in the WebView constructor.
+    private static volatile boolean sEnforceThreadChecking = false;
 
     /**
-     * Construct a new WebView with a Context object.
-     * @param context A Context object used to access application assets.
+     *  Transportation object for returning WebView across thread boundaries.
+     */
+    public class WebViewTransport {
+        private WebView mWebview;
+
+        /**
+         * Sets the WebView to the transportation object.
+         *
+         * @param webview the WebView to transport
+         */
+        public synchronized void setWebView(WebView webview) {
+            mWebview = webview;
+        }
+
+        /**
+         * Gets the WebView object.
+         *
+         * @return the transported WebView object
+         */
+        public synchronized WebView getWebView() {
+            return mWebview;
+        }
+    }
+
+    /**
+     * URI scheme for telephone number.
+     */
+    public static final String SCHEME_TEL = "tel:";
+    /**
+     * URI scheme for email address.
+     */
+    public static final String SCHEME_MAILTO = "mailto:";
+    /**
+     * URI scheme for map address.
+     */
+    public static final String SCHEME_GEO = "geo:0,0?q=";
+
+    /**
+     * Interface to listen for find results.
+     */
+    public interface FindListener {
+        /**
+         * Notifies the listener about progress made by a find operation.
+         *
+         * @param activeMatchOrdinal the zero-based ordinal of the currently selected match
+         * @param numberOfMatches how many matches have been found
+         * @param isDoneCounting whether the find operation has actually completed. The listener
+         *                       may be notified multiple times while the
+         *                       operation is underway, and the numberOfMatches
+         *                       value should not be considered final unless
+         *                       isDoneCounting is {@code true}.
+         */
+        public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+            boolean isDoneCounting);
+    }
+
+    /**
+     * Callback interface supplied to {@link #postVisualStateCallback} for receiving
+     * notifications about the visual state.
+     */
+    public static abstract class VisualStateCallback {
+        /**
+         * Invoked when the visual state is ready to be drawn in the next {@link #onDraw}.
+         *
+         * @param requestId The identifier passed to {@link #postVisualStateCallback} when this
+         *                  callback was posted.
+         */
+        public abstract void onComplete(long requestId);
+    }
+
+    /**
+     * Interface to listen for new pictures as they change.
+     *
+     * @deprecated This interface is now obsolete.
+     */
+    @Deprecated
+    public interface PictureListener {
+        /**
+         * Used to provide notification that the WebView's picture has changed.
+         * See {@link WebView#capturePicture} for details of the picture.
+         *
+         * @param view the WebView that owns the picture
+         * @param picture the new picture. Applications targeting
+         *     {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
+         *     will always receive a {@code null} Picture.
+         * @deprecated Deprecated due to internal changes.
+         */
+        @Deprecated
+        void onNewPicture(WebView view, @Nullable Picture picture);
+    }
+
+    public static class HitTestResult {
+        /**
+         * Default HitTestResult, where the target is unknown.
+         */
+        public static final int UNKNOWN_TYPE = 0;
+        /**
+         * @deprecated This type is no longer used.
+         */
+        @Deprecated
+        public static final int ANCHOR_TYPE = 1;
+        /**
+         * HitTestResult for hitting a phone number.
+         */
+        public static final int PHONE_TYPE = 2;
+        /**
+         * HitTestResult for hitting a map address.
+         */
+        public static final int GEO_TYPE = 3;
+        /**
+         * HitTestResult for hitting an email address.
+         */
+        public static final int EMAIL_TYPE = 4;
+        /**
+         * HitTestResult for hitting an HTML::img tag.
+         */
+        public static final int IMAGE_TYPE = 5;
+        /**
+         * @deprecated This type is no longer used.
+         */
+        @Deprecated
+        public static final int IMAGE_ANCHOR_TYPE = 6;
+        /**
+         * HitTestResult for hitting a HTML::a tag with src=http.
+         */
+        public static final int SRC_ANCHOR_TYPE = 7;
+        /**
+         * HitTestResult for hitting a HTML::a tag with src=http + HTML::img.
+         */
+        public static final int SRC_IMAGE_ANCHOR_TYPE = 8;
+        /**
+         * HitTestResult for hitting an edit text area.
+         */
+        public static final int EDIT_TEXT_TYPE = 9;
+
+        private int mType;
+        private String mExtra;
+
+        /**
+         * @hide Only for use by WebViewProvider implementations
+         */
+        @SystemApi
+        public HitTestResult() {
+            mType = UNKNOWN_TYPE;
+        }
+
+        /**
+         * @hide Only for use by WebViewProvider implementations
+         */
+        @SystemApi
+        public void setType(int type) {
+            mType = type;
+        }
+
+        /**
+         * @hide Only for use by WebViewProvider implementations
+         */
+        @SystemApi
+        public void setExtra(String extra) {
+            mExtra = extra;
+        }
+
+        /**
+         * Gets the type of the hit test result. See the XXX_TYPE constants
+         * defined in this class.
+         *
+         * @return the type of the hit test result
+         */
+        public int getType() {
+            return mType;
+        }
+
+        /**
+         * Gets additional type-dependant information about the result. See
+         * {@link WebView#getHitTestResult()} for details. May either be {@code null}
+         * or contain extra information about this result.
+         *
+         * @return additional type-dependant information about the result
+         */
+        @Nullable
+        public String getExtra() {
+            return mExtra;
+        }
+    }
+
+    /**
+     * Constructs a new WebView with a Context object.
+     *
+     * @param context a Context object used to access application assets
      */
     public WebView(Context context) {
         this(context, null);
     }
 
     /**
-     * Construct a new WebView with layout parameters.
-     * @param context A Context object used to access application assets.
-     * @param attrs An AttributeSet passed to our parent.
+     * Constructs a new WebView with layout parameters.
+     *
+     * @param context a Context object used to access application assets
+     * @param attrs an AttributeSet passed to our parent
      */
     public WebView(Context context, AttributeSet attrs) {
         this(context, attrs, com.android.internal.R.attr.webViewStyle);
     }
 
     /**
-     * Construct a new WebView with layout parameters and a default style.
-     * @param context A Context object used to access application assets.
-     * @param attrs An AttributeSet passed to our parent.
-     * @param defStyle The default style resource ID.
+     * Constructs a new WebView with layout parameters and a default style.
+     *
+     * @param context a Context object used to access application assets
+     * @param attrs an AttributeSet passed to our parent
+     * @param defStyleAttr an attribute in the current theme that contains a
+     *        reference to a style resource that supplies default values for
+     *        the view. Can be 0 to not look for defaults.
      */
-    public WebView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
+    public WebView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
     }
-    
-    // START FAKE PUBLIC METHODS
-    
+
+    /**
+     * Constructs a new WebView with layout parameters and a default style.
+     *
+     * @param context a Context object used to access application assets
+     * @param attrs an AttributeSet passed to our parent
+     * @param defStyleAttr an attribute in the current theme that contains a
+     *        reference to a style resource that supplies default values for
+     *        the view. Can be 0 to not look for defaults.
+     * @param defStyleRes a resource identifier of a style resource that
+     *        supplies default values for the view, used only if
+     *        defStyleAttr is 0 or can not be found in the theme. Can be 0
+     *        to not look for defaults.
+     */
+    public WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        this(context, attrs, defStyleAttr, defStyleRes, null, false);
+    }
+
+    /**
+     * Constructs a new WebView with layout parameters and a default style.
+     *
+     * @param context a Context object used to access application assets
+     * @param attrs an AttributeSet passed to our parent
+     * @param defStyleAttr an attribute in the current theme that contains a
+     *        reference to a style resource that supplies default values for
+     *        the view. Can be 0 to not look for defaults.
+     * @param privateBrowsing whether this WebView will be initialized in
+     *                        private mode
+     *
+     * @deprecated Private browsing is no longer supported directly via
+     * WebView and will be removed in a future release. Prefer using
+     * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
+     * and {@link WebStorage} for fine-grained control of privacy data.
+     */
+    @Deprecated
+    public WebView(Context context, AttributeSet attrs, int defStyleAttr,
+            boolean privateBrowsing) {
+        this(context, attrs, defStyleAttr, 0, null, privateBrowsing);
+    }
+
+    /**
+     * Constructs a new WebView with layout parameters, a default style and a set
+     * of custom JavaScript interfaces to be added to this WebView at initialization
+     * time. This guarantees that these interfaces will be available when the JS
+     * context is initialized.
+     *
+     * @param context a Context object used to access application assets
+     * @param attrs an AttributeSet passed to our parent
+     * @param defStyleAttr an attribute in the current theme that contains a
+     *        reference to a style resource that supplies default values for
+     *        the view. Can be 0 to not look for defaults.
+     * @param javaScriptInterfaces a Map of interface names, as keys, and
+     *                             object implementing those interfaces, as
+     *                             values
+     * @param privateBrowsing whether this WebView will be initialized in
+     *                        private mode
+     * @hide This is used internally by dumprendertree, as it requires the JavaScript interfaces to
+     *       be added synchronously, before a subsequent loadUrl call takes effect.
+     */
+    protected WebView(Context context, AttributeSet attrs, int defStyleAttr,
+            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
+        this(context, attrs, defStyleAttr, 0, javaScriptInterfaces, privateBrowsing);
+    }
+
+    /**
+     * @hide
+     */
+    @SuppressWarnings("deprecation")  // for super() call into deprecated base class constructor.
+    protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
+            Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        // WebView is important by default, unless app developer overrode attribute.
+        if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
+            setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
+        }
+
+        if (context == null) {
+            throw new IllegalArgumentException("Invalid context argument");
+        }
+        sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
+                Build.VERSION_CODES.JELLY_BEAN_MR2;
+        checkThread();
+
+        ensureProviderCreated();
+        mProvider.init(javaScriptInterfaces, privateBrowsing);
+        // Post condition of creating a webview is the CookieSyncManager.getInstance() is allowed.
+        CookieSyncManager.setGetInstanceIsAllowed();
+    }
+
+    /**
+     * Specifies whether the horizontal scrollbar has overlay style.
+     *
+     * @deprecated This method has no effect.
+     * @param overlay {@code true} if horizontal scrollbar should have overlay style
+     */
+    @Deprecated
     public void setHorizontalScrollbarOverlay(boolean overlay) {
     }
 
+    /**
+     * Specifies whether the vertical scrollbar has overlay style.
+     *
+     * @deprecated This method has no effect.
+     * @param overlay {@code true} if vertical scrollbar should have overlay style
+     */
+    @Deprecated
     public void setVerticalScrollbarOverlay(boolean overlay) {
     }
 
+    /**
+     * Gets whether horizontal scrollbar has overlay style.
+     *
+     * @deprecated This method is now obsolete.
+     * @return {@code true}
+     */
+    @Deprecated
     public boolean overlayHorizontalScrollbar() {
-        return false;
+        // The old implementation defaulted to true, so return true for consistency
+        return true;
     }
 
+    /**
+     * Gets whether vertical scrollbar has overlay style.
+     *
+     * @deprecated This method is now obsolete.
+     * @return {@code false}
+     */
+    @Deprecated
     public boolean overlayVerticalScrollbar() {
+        // The old implementation defaulted to false, so return false for consistency
         return false;
     }
 
-    public void savePassword(String host, String username, String password) {
+    /**
+     * Gets the visible height (in pixels) of the embedded title bar (if any).
+     *
+     * @deprecated This method is now obsolete.
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     */
+    @Deprecated
+    public int getVisibleTitleHeight() {
+        checkThread();
+        return mProvider.getVisibleTitleHeight();
     }
 
+    /**
+     * Gets the SSL certificate for the main top-level page or {@code null} if there is
+     * no certificate (the site is not secure).
+     *
+     * @return the SSL certificate for the main top-level page
+     */
+    @Nullable
+    public SslCertificate getCertificate() {
+        checkThread();
+        return mProvider.getCertificate();
+    }
+
+    /**
+     * Sets the SSL certificate for the main top-level page.
+     *
+     * @deprecated Calling this function has no useful effect, and will be
+     * ignored in future releases.
+     */
+    @Deprecated
+    public void setCertificate(SslCertificate certificate) {
+        checkThread();
+        mProvider.setCertificate(certificate);
+    }
+
+    //-------------------------------------------------------------------------
+    // Methods called by activity
+    //-------------------------------------------------------------------------
+
+    /**
+     * Sets a username and password pair for the specified host. This data is
+     * used by the WebView to autocomplete username and password fields in web
+     * forms. Note that this is unrelated to the credentials used for HTTP
+     * authentication.
+     *
+     * @param host the host that required the credentials
+     * @param username the username for the given host
+     * @param password the password for the given host
+     * @see WebViewDatabase#clearUsernamePassword
+     * @see WebViewDatabase#hasUsernamePassword
+     * @deprecated Saving passwords in WebView will not be supported in future versions.
+     */
+    @Deprecated
+    public void savePassword(String host, String username, String password) {
+        checkThread();
+        mProvider.savePassword(host, username, password);
+    }
+
+    /**
+     * Stores HTTP authentication credentials for a given host and realm to the {@link WebViewDatabase}
+     * instance.
+     *
+     * @param host the host to which the credentials apply
+     * @param realm the realm to which the credentials apply
+     * @param username the username
+     * @param password the password
+     * @deprecated Use {@link WebViewDatabase#setHttpAuthUsernamePassword} instead
+     */
+    @Deprecated
     public void setHttpAuthUsernamePassword(String host, String realm,
             String username, String password) {
+        checkThread();
+        mProvider.setHttpAuthUsernamePassword(host, realm, username, password);
     }
 
+    /**
+     * Retrieves HTTP authentication credentials for a given host and realm from the {@link
+     * WebViewDatabase} instance.
+     * @param host the host to which the credentials apply
+     * @param realm the realm to which the credentials apply
+     * @return the credentials as a String array, if found. The first element
+     *         is the username and the second element is the password. {@code null} if
+     *         no credentials are found.
+     * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead
+     */
+    @Deprecated
+    @Nullable
     public String[] getHttpAuthUsernamePassword(String host, String realm) {
-        return null;
+        checkThread();
+        return mProvider.getHttpAuthUsernamePassword(host, realm);
     }
 
+    /**
+     * Destroys the internal state of this WebView. This method should be called
+     * after this WebView has been removed from the view system. No other
+     * methods may be called on this WebView after destroy.
+     */
     public void destroy() {
+        checkThread();
+        mProvider.destroy();
     }
 
+    /**
+     * Enables platform notifications of data state and proxy changes.
+     * Notifications are enabled by default.
+     *
+     * @deprecated This method is now obsolete.
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     */
+    @Deprecated
     public static void enablePlatformNotifications() {
+        // noop
     }
 
+    /**
+     * Disables platform notifications of data state and proxy changes.
+     * Notifications are enabled by default.
+     *
+     * @deprecated This method is now obsolete.
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     */
+    @Deprecated
     public static void disablePlatformNotifications() {
+        // noop
     }
 
+    /**
+     * Used only by internal tests to free up memory.
+     *
+     * @hide
+     */
+    public static void freeMemoryForTests() {
+        getFactory().getStatics().freeMemoryForTests();
+    }
+
+    /**
+     * Informs WebView of the network state. This is used to set
+     * the JavaScript property window.navigator.isOnline and
+     * generates the online/offline event as specified in HTML5, sec. 5.7.7
+     *
+     * @param networkUp a boolean indicating if network is available
+     */
+    public void setNetworkAvailable(boolean networkUp) {
+        checkThread();
+        mProvider.setNetworkAvailable(networkUp);
+    }
+
+    /**
+     * Saves the state of this WebView used in
+     * {@link android.app.Activity#onSaveInstanceState}. Please note that this
+     * method no longer stores the display data for this WebView. The previous
+     * behavior could potentially leak files if {@link #restoreState} was never
+     * called.
+     *
+     * @param outState the Bundle to store this WebView's state
+     * @return the same copy of the back/forward list used to save the state, {@code null} if the
+     *         method fails.
+     */
+    @Nullable
+    public WebBackForwardList saveState(Bundle outState) {
+        checkThread();
+        return mProvider.saveState(outState);
+    }
+
+    /**
+     * Saves the current display data to the Bundle given. Used in conjunction
+     * with {@link #saveState}.
+     * @param b a Bundle to store the display data
+     * @param dest the file to store the serialized picture data. Will be
+     *             overwritten with this WebView's picture data.
+     * @return {@code true} if the picture was successfully saved
+     * @deprecated This method is now obsolete.
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     */
+    @Deprecated
+    public boolean savePicture(Bundle b, final File dest) {
+        checkThread();
+        return mProvider.savePicture(b, dest);
+    }
+
+    /**
+     * Restores the display data that was saved in {@link #savePicture}. Used in
+     * conjunction with {@link #restoreState}. Note that this will not work if
+     * this WebView is hardware accelerated.
+     *
+     * @param b a Bundle containing the saved display data
+     * @param src the file where the picture data was stored
+     * @return {@code true} if the picture was successfully restored
+     * @deprecated This method is now obsolete.
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     */
+    @Deprecated
+    public boolean restorePicture(Bundle b, File src) {
+        checkThread();
+        return mProvider.restorePicture(b, src);
+    }
+
+    /**
+     * Restores the state of this WebView from the given Bundle. This method is
+     * intended for use in {@link android.app.Activity#onRestoreInstanceState}
+     * and should be called to restore the state of this WebView. If
+     * it is called after this WebView has had a chance to build state (load
+     * pages, create a back/forward list, etc.) there may be undesirable
+     * side-effects. Please note that this method no longer restores the
+     * display data for this WebView.
+     *
+     * @param inState the incoming Bundle of state
+     * @return the restored back/forward list or {@code null} if restoreState failed
+     */
+    @Nullable
+    public WebBackForwardList restoreState(Bundle inState) {
+        checkThread();
+        return mProvider.restoreState(inState);
+    }
+
+    /**
+     * Loads the given URL with the specified additional HTTP headers.
+     * <p>
+     * Also see compatibility note on {@link #evaluateJavascript}.
+     *
+     * @param url the URL of the resource to load
+     * @param additionalHttpHeaders the additional headers to be used in the
+     *            HTTP request for this URL, specified as a map from name to
+     *            value. Note that if this map contains any of the headers
+     *            that are set by default by this WebView, such as those
+     *            controlling caching, accept types or the User-Agent, their
+     *            values may be overridden by this WebView's defaults.
+     */
+    public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
+        checkThread();
+        mProvider.loadUrl(url, additionalHttpHeaders);
+    }
+
+    /**
+     * Loads the given URL.
+     * <p>
+     * Also see compatibility note on {@link #evaluateJavascript}.
+     *
+     * @param url the URL of the resource to load
+     */
     public void loadUrl(String url) {
+        checkThread();
+        mProvider.loadUrl(url);
     }
 
-    public void loadData(String data, String mimeType, String encoding) {
+    /**
+     * Loads the URL with postData using "POST" method into this WebView. If url
+     * is not a network URL, it will be loaded with {@link #loadUrl(String)}
+     * instead, ignoring the postData param.
+     *
+     * @param url the URL of the resource to load
+     * @param postData the data will be passed to "POST" request, which must be
+     *     be "application/x-www-form-urlencoded" encoded.
+     */
+    public void postUrl(String url, byte[] postData) {
+        checkThread();
+        if (URLUtil.isNetworkUrl(url)) {
+            mProvider.postUrl(url, postData);
+        } else {
+            mProvider.loadUrl(url);
+        }
     }
 
-    public void loadDataWithBaseURL(String baseUrl, String data,
-            String mimeType, String encoding, String failUrl) {
+    /**
+     * Loads the given data into this WebView using a 'data' scheme URL.
+     * <p>
+     * Note that JavaScript's same origin policy means that script running in a
+     * page loaded using this method will be unable to access content loaded
+     * using any scheme other than 'data', including 'http(s)'. To avoid this
+     * restriction, use {@link
+     * #loadDataWithBaseURL(String,String,String,String,String)
+     * loadDataWithBaseURL()} with an appropriate base URL.
+     * <p>
+     * The encoding parameter specifies whether the data is base64 or URL
+     * encoded. If the data is base64 encoded, the value of the encoding
+     * parameter must be 'base64'. For all other values of the parameter,
+     * including {@code null}, it is assumed that the data uses ASCII encoding for
+     * octets inside the range of safe URL characters and use the standard %xx
+     * hex encoding of URLs for octets outside that range. For example, '#',
+     * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
+     * <p>
+     * The 'data' scheme URL formed by this method uses the default US-ASCII
+     * charset. If you need need to set a different charset, you should form a
+     * 'data' scheme URL which explicitly specifies a charset parameter in the
+     * mediatype portion of the URL and call {@link #loadUrl(String)} instead.
+     * Note that the charset obtained from the mediatype portion of a data URL
+     * always overrides that specified in the HTML or XML document itself.
+     *
+     * @param data a String of data in the given encoding
+     * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
+     *                 defaults to 'text/html'.
+     * @param encoding the encoding of the data
+     */
+    public void loadData(String data, @Nullable String mimeType, @Nullable String encoding) {
+        checkThread();
+        mProvider.loadData(data, mimeType, encoding);
     }
 
+    /**
+     * Loads the given data into this WebView, using baseUrl as the base URL for
+     * the content. The base URL is used both to resolve relative URLs and when
+     * applying JavaScript's same origin policy. The historyUrl is used for the
+     * history entry.
+     * <p>
+     * Note that content specified in this way can access local device files
+     * (via 'file' scheme URLs) only if baseUrl specifies a scheme other than
+     * 'http', 'https', 'ftp', 'ftps', 'about' or 'javascript'.
+     * <p>
+     * If the base URL uses the data scheme, this method is equivalent to
+     * calling {@link #loadData(String,String,String) loadData()} and the
+     * historyUrl is ignored, and the data will be treated as part of a data: URL.
+     * If the base URL uses any other scheme, then the data will be loaded into
+     * the WebView as a plain string (i.e. not part of a data URL) and any URL-encoded
+     * entities in the string will not be decoded.
+     * <p>
+     * Note that the baseUrl is sent in the 'Referer' HTTP header when
+     * requesting subresources (images, etc.) of the page loaded using this method.
+     *
+     * @param baseUrl the URL to use as the page's base URL. If {@code null} defaults to
+     *                'about:blank'.
+     * @param data a String of data in the given encoding
+     * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
+     *                 defaults to 'text/html'.
+     * @param encoding the encoding of the data
+     * @param historyUrl the URL to use as the history entry. If {@code null} defaults
+     *                   to 'about:blank'. If non-null, this must be a valid URL.
+     */
+    public void loadDataWithBaseURL(@Nullable String baseUrl, String data,
+            @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) {
+        checkThread();
+        mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
+    }
+
+    /**
+     * Asynchronously evaluates JavaScript in the context of the currently displayed page.
+     * If non-null, |resultCallback| will be invoked with any result returned from that
+     * execution. This method must be called on the UI thread and the callback will
+     * be made on the UI thread.
+     * <p>
+     * Compatibility note. Applications targeting {@link android.os.Build.VERSION_CODES#N} or
+     * later, JavaScript state from an empty WebView is no longer persisted across navigations like
+     * {@link #loadUrl(String)}. For example, global variables and functions defined before calling
+     * {@link #loadUrl(String)} will not exist in the loaded page. Applications should use
+     * {@link #addJavascriptInterface} instead to persist JavaScript objects across navigations.
+     *
+     * @param script the JavaScript to execute.
+     * @param resultCallback A callback to be invoked when the script execution
+     *                       completes with the result of the execution (if any).
+     *                       May be {@code null} if no notification of the result is required.
+     */
+    public void evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback) {
+        checkThread();
+        mProvider.evaluateJavaScript(script, resultCallback);
+    }
+
+    /**
+     * Saves the current view as a web archive.
+     *
+     * @param filename the filename where the archive should be placed
+     */
+    public void saveWebArchive(String filename) {
+        checkThread();
+        mProvider.saveWebArchive(filename);
+    }
+
+    /**
+     * Saves the current view as a web archive.
+     *
+     * @param basename the filename where the archive should be placed
+     * @param autoname if {@code false}, takes basename to be a file. If {@code true}, basename
+     *                 is assumed to be a directory in which a filename will be
+     *                 chosen according to the URL of the current page.
+     * @param callback called after the web archive has been saved. The
+     *                 parameter for onReceiveValue will either be the filename
+     *                 under which the file was saved, or {@code null} if saving the
+     *                 file failed.
+     */
+    public void saveWebArchive(String basename, boolean autoname, @Nullable ValueCallback<String>
+            callback) {
+        checkThread();
+        mProvider.saveWebArchive(basename, autoname, callback);
+    }
+
+    /**
+     * Stops the current load.
+     */
     public void stopLoading() {
+        checkThread();
+        mProvider.stopLoading();
     }
 
+    /**
+     * Reloads the current URL.
+     */
     public void reload() {
+        checkThread();
+        mProvider.reload();
     }
 
+    /**
+     * Gets whether this WebView has a back history item.
+     *
+     * @return {@code true} if this WebView has a back history item
+     */
     public boolean canGoBack() {
-        return false;
+        checkThread();
+        return mProvider.canGoBack();
     }
 
+    /**
+     * Goes back in the history of this WebView.
+     */
     public void goBack() {
+        checkThread();
+        mProvider.goBack();
     }
 
+    /**
+     * Gets whether this WebView has a forward history item.
+     *
+     * @return {@code true} if this WebView has a forward history item
+     */
     public boolean canGoForward() {
-        return false;
+        checkThread();
+        return mProvider.canGoForward();
     }
 
+    /**
+     * Goes forward in the history of this WebView.
+     */
     public void goForward() {
+        checkThread();
+        mProvider.goForward();
     }
 
+    /**
+     * Gets whether the page can go back or forward the given
+     * number of steps.
+     *
+     * @param steps the negative or positive number of steps to move the
+     *              history
+     */
     public boolean canGoBackOrForward(int steps) {
-        return false;
+        checkThread();
+        return mProvider.canGoBackOrForward(steps);
     }
 
+    /**
+     * Goes to the history item that is the number of steps away from
+     * the current item. Steps is negative if backward and positive
+     * if forward.
+     *
+     * @param steps the number of steps to take back or forward in the back
+     *              forward list
+     */
     public void goBackOrForward(int steps) {
+        checkThread();
+        mProvider.goBackOrForward(steps);
     }
 
+    /**
+     * Gets whether private browsing is enabled in this WebView.
+     */
+    public boolean isPrivateBrowsingEnabled() {
+        checkThread();
+        return mProvider.isPrivateBrowsingEnabled();
+    }
+
+    /**
+     * Scrolls the contents of this WebView up by half the view size.
+     *
+     * @param top {@code true} to jump to the top of the page
+     * @return {@code true} if the page was scrolled
+     */
     public boolean pageUp(boolean top) {
-        return false;
+        checkThread();
+        return mProvider.pageUp(top);
     }
-    
+
+    /**
+     * Scrolls the contents of this WebView down by half the page size.
+     *
+     * @param bottom {@code true} to jump to bottom of page
+     * @return {@code true} if the page was scrolled
+     */
     public boolean pageDown(boolean bottom) {
-        return false;
+        checkThread();
+        return mProvider.pageDown(bottom);
     }
 
+    /**
+     * Posts a {@link VisualStateCallback}, which will be called when
+     * the current state of the WebView is ready to be drawn.
+     *
+     * <p>Because updates to the DOM are processed asynchronously, updates to the DOM may not
+     * immediately be reflected visually by subsequent {@link WebView#onDraw} invocations. The
+     * {@link VisualStateCallback} provides a mechanism to notify the caller when the contents of
+     * the DOM at the current time are ready to be drawn the next time the {@link WebView}
+     * draws.
+     *
+     * <p>The next draw after the callback completes is guaranteed to reflect all the updates to the
+     * DOM up to the point at which the {@link VisualStateCallback} was posted, but it may also
+     * contain updates applied after the callback was posted.
+     *
+     * <p>The state of the DOM covered by this API includes the following:
+     * <ul>
+     * <li>primitive HTML elements (div, img, span, etc..)</li>
+     * <li>images</li>
+     * <li>CSS animations</li>
+     * <li>WebGL</li>
+     * <li>canvas</li>
+     * </ul>
+     * It does not include the state of:
+     * <ul>
+     * <li>the video tag</li>
+     * </ul>
+     *
+     * <p>To guarantee that the {@link WebView} will successfully render the first frame
+     * after the {@link VisualStateCallback#onComplete} method has been called a set of conditions
+     * must be met:
+     * <ul>
+     * <li>If the {@link WebView}'s visibility is set to {@link View#VISIBLE VISIBLE} then
+     * the {@link WebView} must be attached to the view hierarchy.</li>
+     * <li>If the {@link WebView}'s visibility is set to {@link View#INVISIBLE INVISIBLE}
+     * then the {@link WebView} must be attached to the view hierarchy and must be made
+     * {@link View#VISIBLE VISIBLE} from the {@link VisualStateCallback#onComplete} method.</li>
+     * <li>If the {@link WebView}'s visibility is set to {@link View#GONE GONE} then the
+     * {@link WebView} must be attached to the view hierarchy and its
+     * {@link AbsoluteLayout.LayoutParams LayoutParams}'s width and height need to be set to fixed
+     * values and must be made {@link View#VISIBLE VISIBLE} from the
+     * {@link VisualStateCallback#onComplete} method.</li>
+     * </ul>
+     *
+     * <p>When using this API it is also recommended to enable pre-rasterization if the {@link
+     * WebView} is off screen to avoid flickering. See {@link WebSettings#setOffscreenPreRaster} for
+     * more details and do consider its caveats.
+     *
+     * @param requestId An id that will be returned in the callback to allow callers to match
+     *                  requests with callbacks.
+     * @param callback  The callback to be invoked.
+     */
+    public void postVisualStateCallback(long requestId, VisualStateCallback callback) {
+        checkThread();
+        mProvider.insertVisualStateCallback(requestId, callback);
+    }
+
+    /**
+     * Clears this WebView so that onDraw() will draw nothing but white background,
+     * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
+     * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
+     *             and release page resources (including any running JavaScript).
+     */
+    @Deprecated
     public void clearView() {
+        checkThread();
+        mProvider.clearView();
     }
-    
+
+    /**
+     * Gets a new picture that captures the current contents of this WebView.
+     * The picture is of the entire document being displayed, and is not
+     * limited to the area currently displayed by this WebView. Also, the
+     * picture is a static copy and is unaffected by later changes to the
+     * content being displayed.
+     * <p>
+     * Note that due to internal changes, for API levels between
+     * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and
+     * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the
+     * picture does not include fixed position elements or scrollable divs.
+     * <p>
+     * Note that from {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} the returned picture
+     * should only be drawn into bitmap-backed Canvas - using any other type of Canvas will involve
+     * additional conversion at a cost in memory and performance. Also the
+     * {@link android.graphics.Picture#createFromStream} and
+     * {@link android.graphics.Picture#writeToStream} methods are not supported on the
+     * returned object.
+     *
+     * @deprecated Use {@link #onDraw} to obtain a bitmap snapshot of the WebView, or
+     * {@link #saveWebArchive} to save the content to a file.
+     *
+     * @return a picture that captures the current contents of this WebView
+     */
+    @Deprecated
     public Picture capturePicture() {
-        return null;
+        checkThread();
+        return mProvider.capturePicture();
     }
 
+    /**
+     * @deprecated Use {@link #createPrintDocumentAdapter(String)} which requires user
+     *             to provide a print document name.
+     */
+    @Deprecated
+    public PrintDocumentAdapter createPrintDocumentAdapter() {
+        checkThread();
+        return mProvider.createPrintDocumentAdapter("default");
+    }
+
+    /**
+     * Creates a PrintDocumentAdapter that provides the content of this WebView for printing.
+     *
+     * The adapter works by converting the WebView contents to a PDF stream. The WebView cannot
+     * be drawn during the conversion process - any such draws are undefined. It is recommended
+     * to use a dedicated off screen WebView for the printing. If necessary, an application may
+     * temporarily hide a visible WebView by using a custom PrintDocumentAdapter instance
+     * wrapped around the object returned and observing the onStart and onFinish methods. See
+     * {@link android.print.PrintDocumentAdapter} for more information.
+     *
+     * @param documentName  The user-facing name of the printed document. See
+     *                      {@link android.print.PrintDocumentInfo}
+     */
+    public PrintDocumentAdapter createPrintDocumentAdapter(String documentName) {
+        checkThread();
+        return mProvider.createPrintDocumentAdapter(documentName);
+    }
+
+    /**
+     * Gets the current scale of this WebView.
+     *
+     * @return the current scale
+     *
+     * @deprecated This method is prone to inaccuracy due to race conditions
+     * between the web rendering and UI threads; prefer
+     * {@link WebViewClient#onScaleChanged}.
+     */
+    @Deprecated
+    @ViewDebug.ExportedProperty(category = "webview")
     public float getScale() {
-        return 0;
+        checkThread();
+        return mProvider.getScale();
     }
 
+    /**
+     * Sets the initial scale for this WebView. 0 means default.
+     * The behavior for the default scale depends on the state of
+     * {@link WebSettings#getUseWideViewPort()} and
+     * {@link WebSettings#getLoadWithOverviewMode()}.
+     * If the content fits into the WebView control by width, then
+     * the zoom is set to 100%. For wide content, the behavior
+     * depends on the state of {@link WebSettings#getLoadWithOverviewMode()}.
+     * If its value is {@code true}, the content will be zoomed out to be fit
+     * by width into the WebView control, otherwise not.
+     *
+     * If initial scale is greater than 0, WebView starts with this value
+     * as initial scale.
+     * Please note that unlike the scale properties in the viewport meta tag,
+     * this method doesn't take the screen density into account.
+     *
+     * @param scaleInPercent the initial scale in percent
+     */
     public void setInitialScale(int scaleInPercent) {
+        checkThread();
+        mProvider.setInitialScale(scaleInPercent);
     }
 
+    /**
+     * Invokes the graphical zoom picker widget for this WebView. This will
+     * result in the zoom widget appearing on the screen to control the zoom
+     * level of this WebView.
+     */
     public void invokeZoomPicker() {
+        checkThread();
+        mProvider.invokeZoomPicker();
     }
 
-    public void requestFocusNodeHref(Message hrefMsg) {
+    /**
+     * Gets a HitTestResult based on the current cursor node. If a HTML::a
+     * tag is found and the anchor has a non-JavaScript URL, the HitTestResult
+     * type is set to SRC_ANCHOR_TYPE and the URL is set in the "extra" field.
+     * If the anchor does not have a URL or if it is a JavaScript URL, the type
+     * will be UNKNOWN_TYPE and the URL has to be retrieved through
+     * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is
+     * found, the HitTestResult type is set to IMAGE_TYPE and the URL is set in
+     * the "extra" field. A type of
+     * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a URL that has an image as
+     * a child node. If a phone number is found, the HitTestResult type is set
+     * to PHONE_TYPE and the phone number is set in the "extra" field of
+     * HitTestResult. If a map address is found, the HitTestResult type is set
+     * to GEO_TYPE and the address is set in the "extra" field of HitTestResult.
+     * If an email address is found, the HitTestResult type is set to EMAIL_TYPE
+     * and the email is set in the "extra" field of HitTestResult. Otherwise,
+     * HitTestResult type is set to UNKNOWN_TYPE.
+     */
+    public HitTestResult getHitTestResult() {
+        checkThread();
+        return mProvider.getHitTestResult();
     }
 
+    /**
+     * Requests the anchor or image element URL at the last tapped point.
+     * If hrefMsg is {@code null}, this method returns immediately and does not
+     * dispatch hrefMsg to its target. If the tapped point hits an image,
+     * an anchor, or an image in an anchor, the message associates
+     * strings in named keys in its data. The value paired with the key
+     * may be an empty string.
+     *
+     * @param hrefMsg the message to be dispatched with the result of the
+     *                request. The message data contains three keys. "url"
+     *                returns the anchor's href attribute. "title" returns the
+     *                anchor's text. "src" returns the image's src attribute.
+     */
+    public void requestFocusNodeHref(@Nullable Message hrefMsg) {
+        checkThread();
+        mProvider.requestFocusNodeHref(hrefMsg);
+    }
+
+    /**
+     * Requests the URL of the image last touched by the user. msg will be sent
+     * to its target with a String representing the URL as its object.
+     *
+     * @param msg the message to be dispatched with the result of the request
+     *            as the data member with "url" as key. The result can be {@code null}.
+     */
     public void requestImageRef(Message msg) {
+        checkThread();
+        mProvider.requestImageRef(msg);
     }
 
+    /**
+     * Gets the URL for the current page. This is not always the same as the URL
+     * passed to WebViewClient.onPageStarted because although the load for
+     * that URL has begun, the current page may not have changed.
+     *
+     * @return the URL for the current page
+     */
+    @ViewDebug.ExportedProperty(category = "webview")
     public String getUrl() {
-        return null;
+        checkThread();
+        return mProvider.getUrl();
     }
 
+    /**
+     * Gets the original URL for the current page. This is not always the same
+     * as the URL passed to WebViewClient.onPageStarted because although the
+     * load for that URL has begun, the current page may not have changed.
+     * Also, there may have been redirects resulting in a different URL to that
+     * originally requested.
+     *
+     * @return the URL that was originally requested for the current page
+     */
+    @ViewDebug.ExportedProperty(category = "webview")
+    public String getOriginalUrl() {
+        checkThread();
+        return mProvider.getOriginalUrl();
+    }
+
+    /**
+     * Gets the title for the current page. This is the title of the current page
+     * until WebViewClient.onReceivedTitle is called.
+     *
+     * @return the title for the current page
+     */
+    @ViewDebug.ExportedProperty(category = "webview")
     public String getTitle() {
-        return null;
+        checkThread();
+        return mProvider.getTitle();
     }
 
+    /**
+     * Gets the favicon for the current page. This is the favicon of the current
+     * page until WebViewClient.onReceivedIcon is called.
+     *
+     * @return the favicon for the current page
+     */
     public Bitmap getFavicon() {
-        return null;
+        checkThread();
+        return mProvider.getFavicon();
     }
 
+    /**
+     * Gets the touch icon URL for the apple-touch-icon <link> element, or
+     * a URL on this site's server pointing to the standard location of a
+     * touch icon.
+     *
+     * @hide
+     */
+    public String getTouchIconUrl() {
+        return mProvider.getTouchIconUrl();
+    }
+
+    /**
+     * Gets the progress for the current page.
+     *
+     * @return the progress for the current page between 0 and 100
+     */
     public int getProgress() {
-        return 0;
+        checkThread();
+        return mProvider.getProgress();
     }
-    
+
+    /**
+     * Gets the height of the HTML content.
+     *
+     * @return the height of the HTML content
+     */
+    @ViewDebug.ExportedProperty(category = "webview")
     public int getContentHeight() {
-        return 0;
+        checkThread();
+        return mProvider.getContentHeight();
     }
 
+    /**
+     * Gets the width of the HTML content.
+     *
+     * @return the width of the HTML content
+     * @hide
+     */
+    @ViewDebug.ExportedProperty(category = "webview")
+    public int getContentWidth() {
+        return mProvider.getContentWidth();
+    }
+
+    /**
+     * Pauses all layout, parsing, and JavaScript timers for all WebViews. This
+     * is a global requests, not restricted to just this WebView. This can be
+     * useful if the application has been paused.
+     */
     public void pauseTimers() {
+        checkThread();
+        mProvider.pauseTimers();
     }
 
+    /**
+     * Resumes all layout, parsing, and JavaScript timers for all WebViews.
+     * This will resume dispatching all timers.
+     */
     public void resumeTimers() {
+        checkThread();
+        mProvider.resumeTimers();
     }
 
-    public void clearCache() {
+    /**
+     * Does a best-effort attempt to pause any processing that can be paused
+     * safely, such as animations and geolocation. Note that this call
+     * does not pause JavaScript. To pause JavaScript globally, use
+     * {@link #pauseTimers}.
+     *
+     * To resume WebView, call {@link #onResume}.
+     */
+    public void onPause() {
+        checkThread();
+        mProvider.onPause();
     }
 
+    /**
+     * Resumes a WebView after a previous call to {@link #onPause}.
+     */
+    public void onResume() {
+        checkThread();
+        mProvider.onResume();
+    }
+
+    /**
+     * Gets whether this WebView is paused, meaning onPause() was called.
+     * Calling onResume() sets the paused state back to {@code false}.
+     *
+     * @hide
+     */
+    public boolean isPaused() {
+        return mProvider.isPaused();
+    }
+
+    /**
+     * Informs this WebView that memory is low so that it can free any available
+     * memory.
+     * @deprecated Memory caches are automatically dropped when no longer needed, and in response
+     *             to system memory pressure.
+     */
+    @Deprecated
+    public void freeMemory() {
+        checkThread();
+        mProvider.freeMemory();
+    }
+
+    /**
+     * Clears the resource cache. Note that the cache is per-application, so
+     * this will clear the cache for all WebViews used.
+     *
+     * @param includeDiskFiles if {@code false}, only the RAM cache is cleared
+     */
+    public void clearCache(boolean includeDiskFiles) {
+        checkThread();
+        mProvider.clearCache(includeDiskFiles);
+    }
+
+    /**
+     * Removes the autocomplete popup from the currently focused form field, if
+     * present. Note this only affects the display of the autocomplete popup,
+     * it does not remove any saved form data from this WebView's store. To do
+     * that, use {@link WebViewDatabase#clearFormData}.
+     */
     public void clearFormData() {
+        checkThread();
+        mProvider.clearFormData();
     }
 
+    /**
+     * Tells this WebView to clear its internal back/forward list.
+     */
     public void clearHistory() {
+        checkThread();
+        mProvider.clearHistory();
     }
 
+    /**
+     * Clears the SSL preferences table stored in response to proceeding with
+     * SSL certificate errors.
+     */
     public void clearSslPreferences() {
+        checkThread();
+        mProvider.clearSslPreferences();
     }
 
+    /**
+     * Clears the client certificate preferences stored in response
+     * to proceeding/cancelling client cert requests. Note that WebView
+     * automatically clears these preferences when it receives a
+     * {@link KeyChain#ACTION_STORAGE_CHANGED} intent. The preferences are
+     * shared by all the WebViews that are created by the embedder application.
+     *
+     * @param onCleared  A runnable to be invoked when client certs are cleared.
+     *                   The runnable will be called in UI thread.
+     */
+    public static void clearClientCertPreferences(@Nullable Runnable onCleared) {
+        getFactory().getStatics().clearClientCertPreferences(onCleared);
+    }
+
+    /**
+     * Starts Safe Browsing initialization.
+     * <p>
+     * URL loads are not guaranteed to be protected by Safe Browsing until after {@code callback} is
+     * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
+     * devices {@code callback} will receive {@code false}.
+     * <p>
+     * This does not enable the Safe Browsing feature itself, and should only be called if Safe
+     * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
+     * prepares resources used for Safe Browsing.
+     * <p>
+     * This should be called with the Application Context (and will always use the Application
+     * context to do its work regardless).
+     *
+     * @param context Application Context.
+     * @param callback will be called on the UI thread with {@code true} if initialization is
+     * successful, {@code false} otherwise.
+     */
+    public static void startSafeBrowsing(Context context,
+            @Nullable ValueCallback<Boolean> callback) {
+        getFactory().getStatics().initSafeBrowsing(context, callback);
+    }
+
+    /**
+     * Sets the list of domains that are exempt from SafeBrowsing checks. The list is
+     * global for all the WebViews.
+     * <p>
+     * Each rule should take one of these:
+     * <table>
+     * <tr><th> Rule </th> <th> Example </th> <th> Matches Subdomain</th> </tr>
+     * <tr><td> HOSTNAME </td> <td> example.com </td> <td> Yes </td> </tr>
+     * <tr><td> .HOSTNAME </td> <td> .example.com </td> <td> No </td> </tr>
+     * <tr><td> IPV4_LITERAL </td> <td> 192.168.1.1 </td> <td> No </td></tr>
+     * <tr><td> IPV6_LITERAL_WITH_BRACKETS </td><td>[10:20:30:40:50:60:70:80]</td><td>No</td></tr>
+     * </table>
+     * <p>
+     * All other rules, including wildcards, are invalid.
+     *
+     * @param urls the list of URLs
+     * @param callback will be called with {@code true} if URLs are successfully added to the
+     * whitelist. It will be called with {@code false} if any URLs are malformed. The callback will
+     * be run on the UI thread
+     */
+    public static void setSafeBrowsingWhitelist(@NonNull List<String> urls,
+            @Nullable ValueCallback<Boolean> callback) {
+        getFactory().getStatics().setSafeBrowsingWhitelist(urls, callback);
+    }
+
+    /**
+     * Returns a URL pointing to the privacy policy for Safe Browsing reporting.
+     *
+     * @return the url pointing to a privacy policy document which can be displayed to users.
+     */
+    @NonNull
+    public static Uri getSafeBrowsingPrivacyPolicyUrl() {
+        return getFactory().getStatics().getSafeBrowsingPrivacyPolicyUrl();
+    }
+
+    /**
+     * Gets the WebBackForwardList for this WebView. This contains the
+     * back/forward list for use in querying each item in the history stack.
+     * This is a copy of the private WebBackForwardList so it contains only a
+     * snapshot of the current state. Multiple calls to this method may return
+     * different objects. The object returned from this method will not be
+     * updated to reflect any new state.
+     */
+    public WebBackForwardList copyBackForwardList() {
+        checkThread();
+        return mProvider.copyBackForwardList();
+
+    }
+
+    /**
+     * Registers the listener to be notified as find-on-page operations
+     * progress. This will replace the current listener.
+     *
+     * @param listener an implementation of {@link FindListener}
+     */
+    public void setFindListener(FindListener listener) {
+        checkThread();
+        setupFindListenerIfNeeded();
+        mFindListener.mUserFindListener = listener;
+    }
+
+    /**
+     * Highlights and scrolls to the next match found by
+     * {@link #findAllAsync}, wrapping around page boundaries as necessary.
+     * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)}
+     * has not been called yet, or if {@link #clearMatches} has been called since the
+     * last find operation, this function does nothing.
+     *
+     * @param forward the direction to search
+     * @see #setFindListener
+     */
+    public void findNext(boolean forward) {
+        checkThread();
+        mProvider.findNext(forward);
+    }
+
+    /**
+     * Finds all instances of find on the page and highlights them.
+     * Notifies any registered {@link FindListener}.
+     *
+     * @param find the string to find
+     * @return the number of occurrences of the String "find" that were found
+     * @deprecated {@link #findAllAsync} is preferred.
+     * @see #setFindListener
+     */
+    @Deprecated
+    public int findAll(String find) {
+        checkThread();
+        StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
+        return mProvider.findAll(find);
+    }
+
+    /**
+     * Finds all instances of find on the page and highlights them,
+     * asynchronously. Notifies any registered {@link FindListener}.
+     * Successive calls to this will cancel any pending searches.
+     *
+     * @param find the string to find.
+     * @see #setFindListener
+     */
+    public void findAllAsync(String find) {
+        checkThread();
+        mProvider.findAllAsync(find);
+    }
+
+    /**
+     * Starts an ActionMode for finding text in this WebView.  Only works if this
+     * WebView is attached to the view system.
+     *
+     * @param text if non-null, will be the initial text to search for.
+     *             Otherwise, the last String searched for in this WebView will
+     *             be used to start.
+     * @param showIme if {@code true}, show the IME, assuming the user will begin typing.
+     *                If {@code false} and text is non-null, perform a find all.
+     * @return {@code true} if the find dialog is shown, {@code false} otherwise
+     * @deprecated This method does not work reliably on all Android versions;
+     *             implementing a custom find dialog using WebView.findAllAsync()
+     *             provides a more robust solution.
+     */
+    @Deprecated
+    public boolean showFindDialog(@Nullable String text, boolean showIme) {
+        checkThread();
+        return mProvider.showFindDialog(text, showIme);
+    }
+
+    /**
+     * Gets the first substring consisting of the address of a physical
+     * location. Currently, only addresses in the United States are detected,
+     * and consist of:
+     * <ul>
+     *   <li>a house number</li>
+     *   <li>a street name</li>
+     *   <li>a street type (Road, Circle, etc), either spelled out or
+     *       abbreviated</li>
+     *   <li>a city name</li>
+     *   <li>a state or territory, either spelled out or two-letter abbr</li>
+     *   <li>an optional 5 digit or 9 digit zip code</li>
+     * </ul>
+     * All names must be correctly capitalized, and the zip code, if present,
+     * must be valid for the state. The street type must be a standard USPS
+     * spelling or abbreviation. The state or territory must also be spelled
+     * or abbreviated using USPS standards. The house number may not exceed
+     * five digits.
+     *
+     * @param addr the string to search for addresses
+     * @return the address, or if no address is found, {@code null}
+     */
+    @Nullable
     public static String findAddress(String addr) {
-        return null;
+        // TODO: Rewrite this in Java so it is not needed to start up chromium
+        // Could also be deprecated
+        return getFactory().getStatics().findAddress(addr);
     }
 
+    /**
+     * For apps targeting the L release, WebView has a new default behavior that reduces
+     * memory footprint and increases performance by intelligently choosing
+     * the portion of the HTML document that needs to be drawn. These
+     * optimizations are transparent to the developers. However, under certain
+     * circumstances, an App developer may want to disable them:
+     * <ol>
+     *   <li>When an app uses {@link #onDraw} to do own drawing and accesses portions
+     *       of the page that is way outside the visible portion of the page.</li>
+     *   <li>When an app uses {@link #capturePicture} to capture a very large HTML document.
+     *       Note that capturePicture is a deprecated API.</li>
+     * </ol>
+     * Enabling drawing the entire HTML document has a significant performance
+     * cost. This method should be called before any WebViews are created.
+     */
+    public static void enableSlowWholeDocumentDraw() {
+        getFactory().getStatics().enableSlowWholeDocumentDraw();
+    }
+
+    /**
+     * Clears the highlighting surrounding text matches created by
+     * {@link #findAllAsync}.
+     */
+    public void clearMatches() {
+        checkThread();
+        mProvider.clearMatches();
+    }
+
+    /**
+     * Queries the document to see if it contains any image references. The
+     * message object will be dispatched with arg1 being set to 1 if images
+     * were found and 0 if the document does not reference any images.
+     *
+     * @param response the message that will be dispatched with the result
+     */
     public void documentHasImages(Message response) {
+        checkThread();
+        mProvider.documentHasImages(response);
     }
 
+    /**
+     * Sets the WebViewClient that will receive various notifications and
+     * requests. This will replace the current handler.
+     *
+     * @param client an implementation of WebViewClient
+     * @see #getWebViewClient
+     */
     public void setWebViewClient(WebViewClient client) {
+        checkThread();
+        mProvider.setWebViewClient(client);
     }
 
+    /**
+     * Gets the WebViewClient.
+     *
+     * @return the WebViewClient, or a default client if not yet set
+     * @see #setWebViewClient
+     */
+    public WebViewClient getWebViewClient() {
+        checkThread();
+        return mProvider.getWebViewClient();
+    }
+
+    /**
+     * Registers the interface to be used when content can not be handled by
+     * the rendering engine, and should be downloaded instead. This will replace
+     * the current handler.
+     *
+     * @param listener an implementation of DownloadListener
+     */
     public void setDownloadListener(DownloadListener listener) {
+        checkThread();
+        mProvider.setDownloadListener(listener);
     }
 
+    /**
+     * Sets the chrome handler. This is an implementation of WebChromeClient for
+     * use in handling JavaScript dialogs, favicons, titles, and the progress.
+     * This will replace the current handler.
+     *
+     * @param client an implementation of WebChromeClient
+     * @see #getWebChromeClient
+     */
     public void setWebChromeClient(WebChromeClient client) {
+        checkThread();
+        mProvider.setWebChromeClient(client);
     }
 
-    public void addJavascriptInterface(Object obj, String interfaceName) {
+    /**
+     * Gets the chrome handler.
+     *
+     * @return the WebChromeClient, or {@code null} if not yet set
+     * @see #setWebChromeClient
+     */
+    @Nullable
+    public WebChromeClient getWebChromeClient() {
+        checkThread();
+        return mProvider.getWebChromeClient();
     }
 
+    /**
+     * Sets the Picture listener. This is an interface used to receive
+     * notifications of a new Picture.
+     *
+     * @param listener an implementation of WebView.PictureListener
+     * @deprecated This method is now obsolete.
+     */
+    @Deprecated
+    public void setPictureListener(PictureListener listener) {
+        checkThread();
+        mProvider.setPictureListener(listener);
+    }
+
+    /**
+     * Injects the supplied Java object into this WebView. The object is
+     * injected into the JavaScript context of the main frame, using the
+     * supplied name. This allows the Java object's methods to be
+     * accessed from JavaScript. For applications targeted to API
+     * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     * and above, only public methods that are annotated with
+     * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
+     * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
+     * all public methods (including the inherited ones) can be accessed, see the
+     * important security note below for implications.
+     * <p> Note that injected objects will not appear in JavaScript until the page is next
+     * (re)loaded. JavaScript should be enabled before injecting the object. For example:
+     * <pre>
+     * class JsObject {
+     *    {@literal @}JavascriptInterface
+     *    public String toString() { return "injectedObject"; }
+     * }
+     * webview.getSettings().setJavaScriptEnabled(true);
+     * webView.addJavascriptInterface(new JsObject(), "injectedObject");
+     * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
+     * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
+     * <p>
+     * <strong>IMPORTANT:</strong>
+     * <ul>
+     * <li> This method can be used to allow JavaScript to control the host
+     * application. This is a powerful feature, but also presents a security
+     * risk for apps targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or earlier.
+     * Apps that target a version later than {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+     * are still vulnerable if the app runs on a device running Android earlier than 4.2.
+     * The most secure way to use this method is to target {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     * and to ensure the method is called only when running on Android 4.2 or later.
+     * With these older versions, JavaScript could use reflection to access an
+     * injected object's public fields. Use of this method in a WebView
+     * containing untrusted content could allow an attacker to manipulate the
+     * host application in unintended ways, executing Java code with the
+     * permissions of the host application. Use extreme care when using this
+     * method in a WebView which could contain untrusted content.</li>
+     * <li> JavaScript interacts with Java object on a private, background
+     * thread of this WebView. Care is therefore required to maintain thread
+     * safety.
+     * </li>
+     * <li> The Java object's fields are not accessible.</li>
+     * <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP}
+     * and above, methods of injected Java objects are enumerable from
+     * JavaScript.</li>
+     * </ul>
+     *
+     * @param object the Java object to inject into this WebView's JavaScript
+     *               context. {@code null} values are ignored.
+     * @param name the name used to expose the object in JavaScript
+     */
+    public void addJavascriptInterface(Object object, String name) {
+        checkThread();
+        mProvider.addJavascriptInterface(object, name);
+    }
+
+    /**
+     * Removes a previously injected Java object from this WebView. Note that
+     * the removal will not be reflected in JavaScript until the page is next
+     * (re)loaded. See {@link #addJavascriptInterface}.
+     *
+     * @param name the name used to expose the object in JavaScript
+     */
+    public void removeJavascriptInterface(@NonNull String name) {
+        checkThread();
+        mProvider.removeJavascriptInterface(name);
+    }
+
+    /**
+     * Creates a message channel to communicate with JS and returns the message
+     * ports that represent the endpoints of this message channel. The HTML5 message
+     * channel functionality is described
+     * <a href="https://html.spec.whatwg.org/multipage/comms.html#messagechannel">here
+     * </a>
+     *
+     * <p>The returned message channels are entangled and already in started state.
+     *
+     * @return the two message ports that form the message channel.
+     */
+    public WebMessagePort[] createWebMessageChannel() {
+        checkThread();
+        return mProvider.createWebMessageChannel();
+    }
+
+    /**
+     * Post a message to main frame. The embedded application can restrict the
+     * messages to a certain target origin. See
+     * <a href="https://html.spec.whatwg.org/multipage/comms.html#posting-messages">
+     * HTML5 spec</a> for how target origin can be used.
+     * <p>
+     * A target origin can be set as a wildcard ("*"). However this is not recommended.
+     * See the page above for security issues.
+     *
+     * @param message the WebMessage
+     * @param targetOrigin the target origin.
+     */
+    public void postWebMessage(WebMessage message, Uri targetOrigin) {
+        checkThread();
+        mProvider.postMessageToMainFrame(message, targetOrigin);
+    }
+
+    /**
+     * Gets the WebSettings object used to control the settings for this
+     * WebView.
+     *
+     * @return a WebSettings object that can be used to control this WebView's
+     *         settings
+     */
+    public WebSettings getSettings() {
+        checkThread();
+        return mProvider.getSettings();
+    }
+
+    /**
+     * Enables debugging of web contents (HTML / CSS / JavaScript)
+     * loaded into any WebViews of this application. This flag can be enabled
+     * in order to facilitate debugging of web layouts and JavaScript
+     * code running inside WebViews. Please refer to WebView documentation
+     * for the debugging guide.
+     *
+     * The default is {@code false}.
+     *
+     * @param enabled whether to enable web contents debugging
+     */
+    public static void setWebContentsDebuggingEnabled(boolean enabled) {
+        getFactory().getStatics().setWebContentsDebuggingEnabled(enabled);
+    }
+
+    /**
+     * Gets the list of currently loaded plugins.
+     *
+     * @return the list of currently loaded plugins
+     * @deprecated This was used for Gears, which has been deprecated.
+     * @hide
+     */
+    @Deprecated
+    public static synchronized PluginList getPluginList() {
+        return new PluginList();
+    }
+
+    /**
+     * @deprecated This was used for Gears, which has been deprecated.
+     * @hide
+     */
+    @Deprecated
+    public void refreshPlugins(boolean reloadOpenPages) {
+        checkThread();
+    }
+
+    /**
+     * Puts this WebView into text selection mode. Do not rely on this
+     * functionality; it will be deprecated in the future.
+     *
+     * @deprecated This method is now obsolete.
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     */
+    @Deprecated
+    public void emulateShiftHeld() {
+        checkThread();
+    }
+
+    /**
+     * @deprecated WebView no longer needs to implement
+     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
+     */
+    @Override
+    // Cannot add @hide as this can always be accessed via the interface.
+    @Deprecated
+    public void onChildViewAdded(View parent, View child) {}
+
+    /**
+     * @deprecated WebView no longer needs to implement
+     * ViewGroup.OnHierarchyChangeListener.  This method does nothing now.
+     */
+    @Override
+    // Cannot add @hide as this can always be accessed via the interface.
+    @Deprecated
+    public void onChildViewRemoved(View p, View child) {}
+
+    /**
+     * @deprecated WebView should not have implemented
+     * ViewTreeObserver.OnGlobalFocusChangeListener. This method does nothing now.
+     */
+    @Override
+    // Cannot add @hide as this can always be accessed via the interface.
+    @Deprecated
+    public void onGlobalFocusChanged(View oldFocus, View newFocus) {
+    }
+
+    /**
+     * @deprecated Only the default case, {@code true}, will be supported in a future version.
+     */
+    @Deprecated
+    public void setMapTrackballToArrowKeys(boolean setMap) {
+        checkThread();
+        mProvider.setMapTrackballToArrowKeys(setMap);
+    }
+
+
+    public void flingScroll(int vx, int vy) {
+        checkThread();
+        mProvider.flingScroll(vx, vy);
+    }
+
+    /**
+     * Gets the zoom controls for this WebView, as a separate View. The caller
+     * is responsible for inserting this View into the layout hierarchy.
+     * <p/>
+     * API level {@link android.os.Build.VERSION_CODES#CUPCAKE} introduced
+     * built-in zoom mechanisms for the WebView, as opposed to these separate
+     * zoom controls. The built-in mechanisms are preferred and can be enabled
+     * using {@link WebSettings#setBuiltInZoomControls}.
+     *
+     * @deprecated the built-in zoom mechanisms are preferred
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+     */
+    @Deprecated
     public View getZoomControls() {
-        return null;
+        checkThread();
+        return mProvider.getZoomControls();
     }
 
+    /**
+     * Gets whether this WebView can be zoomed in.
+     *
+     * @return {@code true} if this WebView can be zoomed in
+     *
+     * @deprecated This method is prone to inaccuracy due to race conditions
+     * between the web rendering and UI threads; prefer
+     * {@link WebViewClient#onScaleChanged}.
+     */
+    @Deprecated
+    public boolean canZoomIn() {
+        checkThread();
+        return mProvider.canZoomIn();
+    }
+
+    /**
+     * Gets whether this WebView can be zoomed out.
+     *
+     * @return {@code true} if this WebView can be zoomed out
+     *
+     * @deprecated This method is prone to inaccuracy due to race conditions
+     * between the web rendering and UI threads; prefer
+     * {@link WebViewClient#onScaleChanged}.
+     */
+    @Deprecated
+    public boolean canZoomOut() {
+        checkThread();
+        return mProvider.canZoomOut();
+    }
+
+    /**
+     * Performs a zoom operation in this WebView.
+     *
+     * @param zoomFactor the zoom factor to apply. The zoom factor will be clamped to the WebView's
+     * zoom limits. This value must be in the range 0.01 to 100.0 inclusive.
+     */
+    public void zoomBy(float zoomFactor) {
+        checkThread();
+        if (zoomFactor < 0.01)
+            throw new IllegalArgumentException("zoomFactor must be greater than 0.01.");
+        if (zoomFactor > 100.0)
+            throw new IllegalArgumentException("zoomFactor must be less than 100.");
+        mProvider.zoomBy(zoomFactor);
+    }
+
+    /**
+     * Performs zoom in in this WebView.
+     *
+     * @return {@code true} if zoom in succeeds, {@code false} if no zoom changes
+     */
     public boolean zoomIn() {
-        return false;
+        checkThread();
+        return mProvider.zoomIn();
     }
 
+    /**
+     * Performs zoom out in this WebView.
+     *
+     * @return {@code true} if zoom out succeeds, {@code false} if no zoom changes
+     */
     public boolean zoomOut() {
-        return false;
+        checkThread();
+        return mProvider.zoomOut();
+    }
+
+    /**
+     * @deprecated This method is now obsolete.
+     * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     */
+    @Deprecated
+    public void debugDump() {
+        checkThread();
+    }
+
+    /**
+     * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)}
+     * @hide
+     */
+    @Override
+    public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
+        mProvider.dumpViewHierarchyWithProperties(out, level);
+    }
+
+    /**
+     * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)}
+     * @hide
+     */
+    @Override
+    public View findHierarchyView(String className, int hashCode) {
+        return mProvider.findHierarchyView(className, hashCode);
+    }
+
+    /** @hide */
+    @IntDef({
+        RENDERER_PRIORITY_WAIVED,
+        RENDERER_PRIORITY_BOUND,
+        RENDERER_PRIORITY_IMPORTANT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RendererPriority {}
+
+    /**
+     * The renderer associated with this WebView is bound with
+     * {@link Context#BIND_WAIVE_PRIORITY}. At this priority level
+     * {@link WebView} renderers will be strong targets for out of memory
+     * killing.
+     *
+     * Use with {@link #setRendererPriorityPolicy}.
+     */
+    public static final int RENDERER_PRIORITY_WAIVED = 0;
+    /**
+     * The renderer associated with this WebView is bound with
+     * the default priority for services.
+     *
+     * Use with {@link #setRendererPriorityPolicy}.
+     */
+    public static final int RENDERER_PRIORITY_BOUND = 1;
+    /**
+     * The renderer associated with this WebView is bound with
+     * {@link Context#BIND_IMPORTANT}.
+     *
+     * Use with {@link #setRendererPriorityPolicy}.
+     */
+    public static final int RENDERER_PRIORITY_IMPORTANT = 2;
+
+    /**
+     * Set the renderer priority policy for this {@link WebView}. The
+     * priority policy will be used to determine whether an out of
+     * process renderer should be considered to be a target for OOM
+     * killing.
+     *
+     * Because a renderer can be associated with more than one
+     * WebView, the final priority it is computed as the maximum of
+     * any attached WebViews. When a WebView is destroyed it will
+     * cease to be considerered when calculating the renderer
+     * priority. Once no WebViews remain associated with the renderer,
+     * the priority of the renderer will be reduced to
+     * {@link #RENDERER_PRIORITY_WAIVED}.
+     *
+     * The default policy is to set the priority to
+     * {@link #RENDERER_PRIORITY_IMPORTANT} regardless of visibility,
+     * and this should not be changed unless the caller also handles
+     * renderer crashes with
+     * {@link WebViewClient#onRenderProcessGone}. Any other setting
+     * will result in WebView renderers being killed by the system
+     * more aggressively than the application.
+     *
+     * @param rendererRequestedPriority the minimum priority at which
+     *        this WebView desires the renderer process to be bound.
+     * @param waivedWhenNotVisible if {@code true}, this flag specifies that
+     *        when this WebView is not visible, it will be treated as
+     *        if it had requested a priority of
+     *        {@link #RENDERER_PRIORITY_WAIVED}.
+     */
+    public void setRendererPriorityPolicy(
+            @RendererPriority int rendererRequestedPriority,
+            boolean waivedWhenNotVisible) {
+        mProvider.setRendererPriorityPolicy(rendererRequestedPriority, waivedWhenNotVisible);
+    }
+
+    /**
+     * Get the requested renderer priority for this WebView.
+     *
+     * @return the requested renderer priority policy.
+     */
+    @RendererPriority
+    public int getRendererRequestedPriority() {
+        return mProvider.getRendererRequestedPriority();
+    }
+
+    /**
+     * Return whether this WebView requests a priority of
+     * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+     *
+     * @return whether this WebView requests a priority of
+     * {@link #RENDERER_PRIORITY_WAIVED} when not visible.
+     */
+    public boolean getRendererPriorityWaivedWhenNotVisible() {
+        return mProvider.getRendererPriorityWaivedWhenNotVisible();
+    }
+
+    /**
+     * Sets the {@link TextClassifier} for this WebView.
+     */
+    public void setTextClassifier(@Nullable TextClassifier textClassifier) {
+        mProvider.setTextClassifier(textClassifier);
+    }
+
+    /**
+     * Returns the {@link TextClassifier} used by this WebView.
+     * If no TextClassifier has been set, this WebView uses the default set by the system.
+     */
+    @NonNull
+    public TextClassifier getTextClassifier() {
+        return mProvider.getTextClassifier();
+    }
+
+    //-------------------------------------------------------------------------
+    // Interface for WebView providers
+    //-------------------------------------------------------------------------
+
+    /**
+     * Gets the WebViewProvider. Used by providers to obtain the underlying
+     * implementation, e.g. when the application responds to
+     * WebViewClient.onCreateWindow() request.
+     *
+     * @hide WebViewProvider is not public API.
+     */
+    @SystemApi
+    public WebViewProvider getWebViewProvider() {
+        return mProvider;
+    }
+
+    /**
+     * Callback interface, allows the provider implementation to access non-public methods
+     * and fields, and make super-class calls in this WebView instance.
+     * @hide Only for use by WebViewProvider implementations
+     */
+    @SystemApi
+    public class PrivateAccess {
+        // ---- Access to super-class methods ----
+        public int super_getScrollBarStyle() {
+            return WebView.super.getScrollBarStyle();
+        }
+
+        public void super_scrollTo(int scrollX, int scrollY) {
+            WebView.super.scrollTo(scrollX, scrollY);
+        }
+
+        public void super_computeScroll() {
+            WebView.super.computeScroll();
+        }
+
+        public boolean super_onHoverEvent(MotionEvent event) {
+            return WebView.super.onHoverEvent(event);
+        }
+
+        public boolean super_performAccessibilityAction(int action, Bundle arguments) {
+            return WebView.super.performAccessibilityActionInternal(action, arguments);
+        }
+
+        public boolean super_performLongClick() {
+            return WebView.super.performLongClick();
+        }
+
+        public boolean super_setFrame(int left, int top, int right, int bottom) {
+            return WebView.super.setFrame(left, top, right, bottom);
+        }
+
+        public boolean super_dispatchKeyEvent(KeyEvent event) {
+            return WebView.super.dispatchKeyEvent(event);
+        }
+
+        public boolean super_onGenericMotionEvent(MotionEvent event) {
+            return WebView.super.onGenericMotionEvent(event);
+        }
+
+        public boolean super_requestFocus(int direction, Rect previouslyFocusedRect) {
+            return WebView.super.requestFocus(direction, previouslyFocusedRect);
+        }
+
+        public void super_setLayoutParams(ViewGroup.LayoutParams params) {
+            WebView.super.setLayoutParams(params);
+        }
+
+        public void super_startActivityForResult(Intent intent, int requestCode) {
+            WebView.super.startActivityForResult(intent, requestCode);
+        }
+
+        // ---- Access to non-public methods ----
+        public void overScrollBy(int deltaX, int deltaY,
+                int scrollX, int scrollY,
+                int scrollRangeX, int scrollRangeY,
+                int maxOverScrollX, int maxOverScrollY,
+                boolean isTouchEvent) {
+            WebView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
+                    maxOverScrollX, maxOverScrollY, isTouchEvent);
+        }
+
+        public void awakenScrollBars(int duration) {
+            WebView.this.awakenScrollBars(duration);
+        }
+
+        public void awakenScrollBars(int duration, boolean invalidate) {
+            WebView.this.awakenScrollBars(duration, invalidate);
+        }
+
+        public float getVerticalScrollFactor() {
+            return WebView.this.getVerticalScrollFactor();
+        }
+
+        public float getHorizontalScrollFactor() {
+            return WebView.this.getHorizontalScrollFactor();
+        }
+
+        public void setMeasuredDimension(int measuredWidth, int measuredHeight) {
+            WebView.this.setMeasuredDimension(measuredWidth, measuredHeight);
+        }
+
+        public void onScrollChanged(int l, int t, int oldl, int oldt) {
+            WebView.this.onScrollChanged(l, t, oldl, oldt);
+        }
+
+        public int getHorizontalScrollbarHeight() {
+            return WebView.this.getHorizontalScrollbarHeight();
+        }
+
+        public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+                int l, int t, int r, int b) {
+            WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
+        }
+
+        // ---- Access to (non-public) fields ----
+        /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */
+        public void setScrollXRaw(int scrollX) {
+            WebView.this.mScrollX = scrollX;
+        }
+
+        /** Raw setter for the scroll Y value, without invoking onScrollChanged handlers etc. */
+        public void setScrollYRaw(int scrollY) {
+            WebView.this.mScrollY = scrollY;
+        }
+
+    }
+
+    //-------------------------------------------------------------------------
+    // Package-private internal stuff
+    //-------------------------------------------------------------------------
+
+    // Only used by android.webkit.FindActionModeCallback.
+    void setFindDialogFindListener(FindListener listener) {
+        checkThread();
+        setupFindListenerIfNeeded();
+        mFindListener.mFindDialogFindListener = listener;
+    }
+
+    // Only used by android.webkit.FindActionModeCallback.
+    void notifyFindDialogDismissed() {
+        checkThread();
+        mProvider.notifyFindDialogDismissed();
+    }
+
+    //-------------------------------------------------------------------------
+    // Private internal stuff
+    //-------------------------------------------------------------------------
+
+    private WebViewProvider mProvider;
+
+    /**
+     * In addition to the FindListener that the user may set via the WebView.setFindListener
+     * API, FindActionModeCallback will register it's own FindListener. We keep them separate
+     * via this class so that the two FindListeners can potentially exist at once.
+     */
+    private class FindListenerDistributor implements FindListener {
+        private FindListener mFindDialogFindListener;
+        private FindListener mUserFindListener;
+
+        @Override
+        public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+                boolean isDoneCounting) {
+            if (mFindDialogFindListener != null) {
+                mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+                        isDoneCounting);
+            }
+
+            if (mUserFindListener != null) {
+                mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+                        isDoneCounting);
+            }
+        }
+    }
+    private FindListenerDistributor mFindListener;
+
+    private void setupFindListenerIfNeeded() {
+        if (mFindListener == null) {
+            mFindListener = new FindListenerDistributor();
+            mProvider.setFindListener(mFindListener);
+        }
+    }
+
+    private void ensureProviderCreated() {
+        checkThread();
+        if (mProvider == null) {
+            // As this can get called during the base class constructor chain, pass the minimum
+            // number of dependencies here; the rest are deferred to init().
+            mProvider = getFactory().createWebView(this, new PrivateAccess());
+        }
+    }
+
+    private static WebViewFactoryProvider getFactory() {
+        return WebViewFactory.getProvider();
+    }
+
+    private final Looper mWebViewThread = Looper.myLooper();
+
+    private void checkThread() {
+        // Ignore mWebViewThread == null because this can be called during in the super class
+        // constructor, before this class's own constructor has even started.
+        if (mWebViewThread != null && Looper.myLooper() != mWebViewThread) {
+            Throwable throwable = new Throwable(
+                    "A WebView method was called on thread '" +
+                    Thread.currentThread().getName() + "'. " +
+                    "All WebView methods must be called on the same thread. " +
+                    "(Expected Looper " + mWebViewThread + " called on " + Looper.myLooper() +
+                    ", FYI main Looper is " + Looper.getMainLooper() + ")");
+            Log.w(LOGTAG, Log.getStackTraceString(throwable));
+            StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
+
+            if (sEnforceThreadChecking) {
+                throw new RuntimeException(throwable);
+            }
+        }
+    }
+
+    //-------------------------------------------------------------------------
+    // Override View methods
+    //-------------------------------------------------------------------------
+
+    // TODO: Add a test that enumerates all methods in ViewDelegte & ScrollDelegate, and ensures
+    // there's a corresponding override (or better, caller) for each of them in here.
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mProvider.getViewDelegate().onAttachedToWindow();
+    }
+
+    /** @hide */
+    @Override
+    protected void onDetachedFromWindowInternal() {
+        mProvider.getViewDelegate().onDetachedFromWindow();
+        super.onDetachedFromWindowInternal();
+    }
+
+    /** @hide */
+    @Override
+    public void onMovedToDisplay(int displayId, Configuration config) {
+        mProvider.getViewDelegate().onMovedToDisplay(displayId, config);
+    }
+
+    @Override
+    public void setLayoutParams(ViewGroup.LayoutParams params) {
+        mProvider.getViewDelegate().setLayoutParams(params);
+    }
+
+    @Override
+    public void setOverScrollMode(int mode) {
+        super.setOverScrollMode(mode);
+        // This method may be called in the constructor chain, before the WebView provider is
+        // created.
+        ensureProviderCreated();
+        mProvider.getViewDelegate().setOverScrollMode(mode);
+    }
+
+    @Override
+    public void setScrollBarStyle(int style) {
+        mProvider.getViewDelegate().setScrollBarStyle(style);
+        super.setScrollBarStyle(style);
+    }
+
+    @Override
+    protected int computeHorizontalScrollRange() {
+        return mProvider.getScrollDelegate().computeHorizontalScrollRange();
+    }
+
+    @Override
+    protected int computeHorizontalScrollOffset() {
+        return mProvider.getScrollDelegate().computeHorizontalScrollOffset();
+    }
+
+    @Override
+    protected int computeVerticalScrollRange() {
+        return mProvider.getScrollDelegate().computeVerticalScrollRange();
+    }
+
+    @Override
+    protected int computeVerticalScrollOffset() {
+        return mProvider.getScrollDelegate().computeVerticalScrollOffset();
+    }
+
+    @Override
+    protected int computeVerticalScrollExtent() {
+        return mProvider.getScrollDelegate().computeVerticalScrollExtent();
+    }
+
+    @Override
+    public void computeScroll() {
+        mProvider.getScrollDelegate().computeScroll();
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        return mProvider.getViewDelegate().onHoverEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return mProvider.getViewDelegate().onTouchEvent(event);
+    }
+
+    @Override
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        return mProvider.getViewDelegate().onGenericMotionEvent(event);
+    }
+
+    @Override
+    public boolean onTrackballEvent(MotionEvent event) {
+        return mProvider.getViewDelegate().onTrackballEvent(event);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return mProvider.getViewDelegate().onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return mProvider.getViewDelegate().onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return mProvider.getViewDelegate().onKeyMultiple(keyCode, repeatCount, event);
+    }
+
+    /*
+    TODO: These are not currently implemented in WebViewClassic, but it seems inconsistent not
+    to be delegating them too.
+
+    @Override
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        return mProvider.getViewDelegate().onKeyPreIme(keyCode, event);
+    }
+    @Override
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        return mProvider.getViewDelegate().onKeyLongPress(keyCode, event);
+    }
+    @Override
+    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+        return mProvider.getViewDelegate().onKeyShortcut(keyCode, event);
+    }
+    */
+
+    @Override
+    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+        AccessibilityNodeProvider provider =
+                mProvider.getViewDelegate().getAccessibilityNodeProvider();
+        return provider == null ? super.getAccessibilityNodeProvider() : provider;
+    }
+
+    @Deprecated
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return mProvider.getViewDelegate().shouldDelayChildPressedState();
+    }
+
+    @Override
+    public CharSequence getAccessibilityClassName() {
+        return WebView.class.getName();
+    }
+
+    @Override
+    public void onProvideVirtualStructure(ViewStructure structure) {
+        mProvider.getViewDelegate().onProvideVirtualStructure(structure);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The {@link ViewStructure} traditionally represents a {@link View}, while for web pages
+     * it represent HTML nodes. Hence, it's necessary to "map" the HTML properties in a way that is
+     * understood by the {@link android.service.autofill.AutofillService} implementations:
+     *
+     * <ol>
+     *   <li>Only the HTML nodes inside a {@code FORM} are generated.
+     *   <li>The source of the HTML is set using {@link ViewStructure#setWebDomain(String)} in the
+     *   node representing the WebView.
+     *   <li>If a web page has multiple {@code FORM}s, only the data for the current form is
+     *   represented&mdash;if the user taps a field from another form, then the current autofill
+     *   context is canceled (by calling {@link android.view.autofill.AutofillManager#cancel()} and
+     *   a new context is created for that {@code FORM}.
+     *   <li>Similarly, if the page has {@code IFRAME} nodes, they are not initially represented in
+     *   the view structure until the user taps a field from a {@code FORM} inside the
+     *   {@code IFRAME}, in which case it would be treated the same way as multiple forms described
+     *   above, except that the {@link ViewStructure#setWebDomain(String) web domain} of the
+     *   {@code FORM} contains the {@code src} attribute from the {@code IFRAME} node.
+     *   <li>The W3C autofill field ({@code autocomplete} tag attribute) maps to
+     *   {@link ViewStructure#setAutofillHints(String[])}.
+     *   <li>If the view is editable, the {@link ViewStructure#setAutofillType(int)} and
+     *   {@link ViewStructure#setAutofillValue(AutofillValue)} must be set.
+     *   <li>The {@code placeholder} attribute maps to {@link ViewStructure#setHint(CharSequence)}.
+     *   <li>Other HTML attributes can be represented through
+     *   {@link ViewStructure#setHtmlInfo(android.view.ViewStructure.HtmlInfo)}.
+     * </ol>
+     *
+     * <p>If the WebView implementation can determine that the value of a field was set statically
+     * (for example, not through Javascript), it should also call
+     * {@code structure.setDataIsSensitive(false)}.
+     *
+     * <p>For example, an HTML form with 2 fields for username and password:
+     *
+     * <pre class="prettyprint">
+     *    &lt;input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username"&gt;
+     *    &lt;input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password"&gt;
+     * </pre>
+     *
+     * <p>Would map to:
+     *
+     * <pre class="prettyprint">
+     *     int index = structure.addChildCount(2);
+     *     ViewStructure username = structure.newChild(index);
+     *     username.setAutofillId(structure.getAutofillId(), 1); // id 1 - first child
+     *     username.setAutofillHints("username");
+     *     username.setHtmlInfo(username.newHtmlInfoBuilder("input")
+     *         .addAttribute("type", "text")
+     *         .addAttribute("name", "username")
+     *         .build());
+     *     username.setHint("Email or username");
+     *     username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+     *     username.setAutofillValue(AutofillValue.forText("Type your username"));
+     *     // Value of the field is not sensitive because it was created statically and not changed.
+     *     username.setDataIsSensitive(false);
+     *
+     *     ViewStructure password = structure.newChild(index + 1);
+     *     username.setAutofillId(structure, 2); // id 2 - second child
+     *     password.setAutofillHints("current-password");
+     *     password.setHtmlInfo(password.newHtmlInfoBuilder("input")
+     *         .addAttribute("type", "password")
+     *         .addAttribute("name", "password")
+     *         .build());
+     *     password.setHint("Password");
+     *     password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
+     * </pre>
+     */
+    @Override
+    public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
+        mProvider.getViewDelegate().onProvideAutofillVirtualStructure(structure, flags);
+    }
+
+    @Override
+    public void autofill(SparseArray<AutofillValue>values) {
+        mProvider.getViewDelegate().autofill(values);
+    }
+
+    /** @hide */
+    @Override
+    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfoInternal(info);
+        mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
+    }
+
+    /** @hide */
+    @Override
+    public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEventInternal(event);
+        mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
+    }
+
+    /** @hide */
+    @Override
+    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+        return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
+    }
+
+    /** @hide */
+    @Override
+    protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+            int l, int t, int r, int b) {
+        mProvider.getViewDelegate().onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
+    }
+
+    @Override
+    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
+        mProvider.getViewDelegate().onOverScrolled(scrollX, scrollY, clampedX, clampedY);
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        mProvider.getViewDelegate().onWindowVisibilityChanged(visibility);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        mProvider.getViewDelegate().onDraw(canvas);
+    }
+
+    @Override
+    public boolean performLongClick() {
+        return mProvider.getViewDelegate().performLongClick();
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        mProvider.getViewDelegate().onConfigurationChanged(newConfig);
+    }
+
+    /**
+     * Creates a new InputConnection for an InputMethod to interact with the WebView.
+     * This is similar to {@link View#onCreateInputConnection} but note that WebView
+     * calls InputConnection methods on a thread other than the UI thread.
+     * If these methods are overridden, then the overriding methods should respect
+     * thread restrictions when calling View methods or accessing data.
+     */
+    @Override
+    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        return mProvider.getViewDelegate().onCreateInputConnection(outAttrs);
+    }
+
+    @Override
+    public boolean onDragEvent(DragEvent event) {
+        return mProvider.getViewDelegate().onDragEvent(event);
+    }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        // This method may be called in the constructor chain, before the WebView provider is
+        // created.
+        ensureProviderCreated();
+        mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility);
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        mProvider.getViewDelegate().onWindowFocusChanged(hasWindowFocus);
+        super.onWindowFocusChanged(hasWindowFocus);
+    }
+
+    @Override
+    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+        mProvider.getViewDelegate().onFocusChanged(focused, direction, previouslyFocusedRect);
+        super.onFocusChanged(focused, direction, previouslyFocusedRect);
+    }
+
+    /** @hide */
+    @Override
+    protected boolean setFrame(int left, int top, int right, int bottom) {
+        return mProvider.getViewDelegate().setFrame(left, top, right, bottom);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int ow, int oh) {
+        super.onSizeChanged(w, h, ow, oh);
+        mProvider.getViewDelegate().onSizeChanged(w, h, ow, oh);
+    }
+
+    @Override
+    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        super.onScrollChanged(l, t, oldl, oldt);
+        mProvider.getViewDelegate().onScrollChanged(l, t, oldl, oldt);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        return mProvider.getViewDelegate().dispatchKeyEvent(event);
+    }
+
+    @Override
+    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        return mProvider.getViewDelegate().requestFocus(direction, previouslyFocusedRect);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mProvider.getViewDelegate().onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
+        return mProvider.getViewDelegate().requestChildRectangleOnScreen(child, rect, immediate);
+    }
+
+    @Override
+    public void setBackgroundColor(int color) {
+        mProvider.getViewDelegate().setBackgroundColor(color);
+    }
+
+    @Override
+    public void setLayerType(int layerType, Paint paint) {
+        super.setLayerType(layerType, paint);
+        mProvider.getViewDelegate().setLayerType(layerType, paint);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        mProvider.getViewDelegate().preDispatchDraw(canvas);
+        super.dispatchDraw(canvas);
+    }
+
+    @Override
+    public void onStartTemporaryDetach() {
+        super.onStartTemporaryDetach();
+        mProvider.getViewDelegate().onStartTemporaryDetach();
+    }
+
+    @Override
+    public void onFinishTemporaryDetach() {
+        super.onFinishTemporaryDetach();
+        mProvider.getViewDelegate().onFinishTemporaryDetach();
+    }
+
+    @Override
+    public Handler getHandler() {
+        return mProvider.getViewDelegate().getHandler(super.getHandler());
+    }
+
+    @Override
+    public View findFocus() {
+        return mProvider.getViewDelegate().findFocus(super.findFocus());
+    }
+
+    /**
+     * If WebView has already been loaded into the current process this method will return the
+     * package that was used to load it. Otherwise, the package that would be used if the WebView
+     * was loaded right now will be returned; this does not cause WebView to be loaded, so this
+     * information may become outdated at any time.
+     * The WebView package changes either when the current WebView package is updated, disabled, or
+     * uninstalled. It can also be changed through a Developer Setting.
+     * If the WebView package changes, any app process that has loaded WebView will be killed. The
+     * next time the app starts and loads WebView it will use the new WebView package instead.
+     * @return the current WebView package, or {@code null} if there is none.
+     */
+    @Nullable
+    public static PackageInfo getCurrentWebViewPackage() {
+        PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
+        if (webviewPackage != null) {
+            return webviewPackage;
+        }
+
+        IWebViewUpdateService service = WebViewFactory.getUpdateService();
+        if (service == null) {
+            return null;
+        }
+        try {
+            return service.getCurrentWebViewPackage();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
+     *
+     * @param requestCode The integer request code originally supplied to
+     *                    startActivityForResult(), allowing you to identify who this
+     *                    result came from.
+     * @param resultCode The integer result code returned by the child activity
+     *                   through its setResult().
+     * @param data An Intent, which can return result data to the caller
+     *               (various data can be attached to Intent "extras").
+     * @hide
+     */
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        mProvider.getViewDelegate().onActivityResult(requestCode, resultCode, data);
+    }
+
+    /** @hide */
+    @Override
+    protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+        super.encodeProperties(encoder);
+
+        checkThread();
+        encoder.addProperty("webview:contentHeight", mProvider.getContentHeight());
+        encoder.addProperty("webview:contentWidth", mProvider.getContentWidth());
+        encoder.addProperty("webview:scale", mProvider.getScale());
+        encoder.addProperty("webview:title", mProvider.getTitle());
+        encoder.addProperty("webview:url", mProvider.getUrl());
+        encoder.addProperty("webview:originalUrl", mProvider.getOriginalUrl());
     }
 }
diff --git a/android/webkit/WebViewFactory.java b/android/webkit/WebViewFactory.java
index 95cb454..797bdfb 100644
--- a/android/webkit/WebViewFactory.java
+++ b/android/webkit/WebViewFactory.java
@@ -25,7 +25,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
-import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
@@ -138,7 +137,7 @@
     }
 
     /**
-     * Load the native library for the given package name iff that package
+     * Load the native library for the given package name if that package
      * name is the same as the one providing the webview.
      */
     public static int loadWebViewNativeLibraryFromPackage(String packageName,
@@ -445,38 +444,17 @@
         }
     }
 
-    private static int prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
-        if (DEBUG) Log.v(LOGTAG, "creating relro files");
-        int numRelros = 0;
-
-        // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
-        // unexpected values will be handled there to ensure that we trigger notifying any process
-        // waiting on relro creation.
-        if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
-            if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
-            WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths[0]);
-            numRelros++;
-        }
-
-        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
-            if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
-            WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths[1]);
-            numRelros++;
-        }
-        return numRelros;
-    }
-
     /**
      * @hide
      */
     public static int onWebViewProviderChanged(PackageInfo packageInfo) {
-        String[] nativeLibs = null;
+        int startedRelroProcesses = 0;
         ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
         try {
             fixupStubApplicationInfo(packageInfo.applicationInfo,
                                      AppGlobals.getInitialApplication().getPackageManager());
 
-            nativeLibs = WebViewLibraryLoader.updateWebViewZygoteVmSize(packageInfo);
+            startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
         } catch (Throwable t) {
             // Log and discard errors at this stage as we must not crash the system server.
             Log.e(LOGTAG, "error preparing webview native library", t);
@@ -484,7 +462,7 @@
 
         WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
 
-        return prepareWebViewInSystemServer(nativeLibs);
+        return startedRelroProcesses;
     }
 
     private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate";
diff --git a/android/webkit/WebViewLibraryLoader.java b/android/webkit/WebViewLibraryLoader.java
index fa1a390..341c69f 100644
--- a/android/webkit/WebViewLibraryLoader.java
+++ b/android/webkit/WebViewLibraryLoader.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -26,6 +28,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 
 import dalvik.system.VMRuntime;
@@ -36,7 +39,11 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
-class WebViewLibraryLoader {
+/**
+ * @hide
+ */
+@VisibleForTesting
+public class WebViewLibraryLoader {
     private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName();
 
     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
@@ -94,7 +101,7 @@
     /**
      * Create a single relro file by invoking an isolated process that to do the actual work.
      */
-    static void createRelroFile(final boolean is64Bit, String nativeLibraryPath) {
+    static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) {
         final String abi =
                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
 
@@ -112,12 +119,12 @@
         };
 
         try {
-            if (nativeLibraryPath == null) {
+            if (nativeLib == null || nativeLib.path == null) {
                 throw new IllegalArgumentException(
                         "Native library paths to the WebView RelRo process must not be null!");
             }
             int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
-                    RelroFileCreator.class.getName(), new String[] { nativeLibraryPath },
+                    RelroFileCreator.class.getName(), new String[] { nativeLib.path },
                     "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
             if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
         } catch (Throwable t) {
@@ -128,56 +135,77 @@
     }
 
     /**
+     * Perform preparations needed to allow loading WebView from an application. This method should
+     * be called whenever we change WebView provider.
+     * @return the number of relro processes started.
+     */
+    static int prepareNativeLibraries(PackageInfo webviewPackageInfo)
+            throws WebViewFactory.MissingWebViewPackageException {
+        WebViewNativeLibrary nativeLib32bit =
+                getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */);
+        WebViewNativeLibrary nativeLib64bit =
+                getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */);
+        updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);
+
+        return createRelros(nativeLib32bit, nativeLib64bit);
+    }
+
+    /**
+     * @return the number of relro processes started.
+     */
+    private static int createRelros(@Nullable WebViewNativeLibrary nativeLib32bit,
+            @Nullable WebViewNativeLibrary nativeLib64bit) {
+        if (DEBUG) Log.v(LOGTAG, "creating relro files");
+        int numRelros = 0;
+
+        if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+            if (nativeLib32bit == null) {
+                Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation.");
+            } else {
+                if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
+                createRelroFile(false /* is64Bit */, nativeLib32bit);
+                numRelros++;
+            }
+        }
+
+        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
+            if (nativeLib64bit == null) {
+                Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation.");
+            } else {
+                if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
+                createRelroFile(true /* is64Bit */, nativeLib64bit);
+                numRelros++;
+            }
+        }
+        return numRelros;
+    }
+
+    /**
      *
      * @return the native WebView libraries in the new WebView APK.
      */
-    static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo)
+    private static void updateWebViewZygoteVmSize(
+            @Nullable WebViewNativeLibrary nativeLib32bit,
+            @Nullable WebViewNativeLibrary nativeLib64bit)
             throws WebViewFactory.MissingWebViewPackageException {
         // Find the native libraries of the new WebView package, to change the size of the
         // memory region in the Zygote reserved for the library.
-        String[] nativeLibs = getWebViewNativeLibraryPaths(packageInfo);
-        if (nativeLibs != null) {
-            long newVmSize = 0L;
+        long newVmSize = 0L;
 
-            for (String path : nativeLibs) {
-                if (path == null || TextUtils.isEmpty(path)) continue;
-                if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
-                File f = new File(path);
-                if (f.exists()) {
-                    newVmSize = Math.max(newVmSize, f.length());
-                    continue;
-                }
-                if (path.contains("!/")) {
-                    String[] split = TextUtils.split(path, "!/");
-                    if (split.length == 2) {
-                        try (ZipFile z = new ZipFile(split[0])) {
-                            ZipEntry e = z.getEntry(split[1]);
-                            if (e != null && e.getMethod() == ZipEntry.STORED) {
-                                newVmSize = Math.max(newVmSize, e.getSize());
-                                continue;
-                            }
-                        }
-                        catch (IOException e) {
-                            Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e);
-                        }
-                    }
-                }
-                Log.e(LOGTAG, "error sizing load for " + path);
-            }
+        if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size);
+        if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size);
 
-            if (DEBUG) {
-                Log.v(LOGTAG, "Based on library size, need " + newVmSize
-                        + " bytes of address space.");
-            }
-            // The required memory can be larger than the file on disk (due to .bss), and an
-            // upgraded version of the library will likely be larger, so always attempt to
-            // reserve twice as much as we think to allow for the library to grow during this
-            // boot cycle.
-            newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
-            Log.d(LOGTAG, "Setting new address space to " + newVmSize);
-            setWebViewZygoteVmSize(newVmSize);
+        if (DEBUG) {
+            Log.v(LOGTAG, "Based on library size, need " + newVmSize
+                    + " bytes of address space.");
         }
-        return nativeLibs;
+        // The required memory can be larger than the file on disk (due to .bss), and an
+        // upgraded version of the library will likely be larger, so always attempt to
+        // reserve twice as much as we think to allow for the library to grow during this
+        // boot cycle.
+        newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
+        Log.d(LOGTAG, "Setting new address space to " + newVmSize);
+        setWebViewZygoteVmSize(newVmSize);
     }
 
     /**
@@ -227,64 +255,78 @@
 
     /**
      * Fetch WebView's native library paths from {@param packageInfo}.
+     * @hide
      */
-    static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo)
-            throws WebViewFactory.MissingWebViewPackageException {
+    @Nullable
+    @VisibleForTesting
+    public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo,
+            boolean is64bit) throws WebViewFactory.MissingWebViewPackageException {
         ApplicationInfo ai = packageInfo.applicationInfo;
         final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai);
 
-        String path32;
-        String path64;
-        boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
-        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
-            // Multi-arch case.
-            if (primaryArchIs64bit) {
-                // Primary arch: 64-bit, secondary: 32-bit.
-                path64 = ai.nativeLibraryDir;
-                path32 = ai.secondaryNativeLibraryDir;
-            } else {
-                // Primary arch: 32-bit, secondary: 64-bit.
-                path64 = ai.secondaryNativeLibraryDir;
-                path32 = ai.nativeLibraryDir;
-            }
-        } else if (primaryArchIs64bit) {
-            // Single-arch 64-bit.
-            path64 = ai.nativeLibraryDir;
-            path32 = "";
-        } else {
-            // Single-arch 32-bit.
-            path32 = ai.nativeLibraryDir;
-            path64 = "";
-        }
+        String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */);
 
-        // Form the full paths to the extracted native libraries.
-        // If libraries were not extracted, try load from APK paths instead.
-        if (!TextUtils.isEmpty(path32)) {
-            path32 += "/" + nativeLibFileName;
-            File f = new File(path32);
-            if (!f.exists()) {
-                path32 = getLoadFromApkPath(ai.sourceDir,
-                                            Build.SUPPORTED_32_BIT_ABIS,
-                                            nativeLibFileName);
-            }
-        }
-        if (!TextUtils.isEmpty(path64)) {
-            path64 += "/" + nativeLibFileName;
-            File f = new File(path64);
-            if (!f.exists()) {
-                path64 = getLoadFromApkPath(ai.sourceDir,
-                                            Build.SUPPORTED_64_BIT_ABIS,
-                                            nativeLibFileName);
-            }
-        }
+        WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName,
+                is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir);
 
-        if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64);
-        return new String[] { path32, path64 };
+        if (DEBUG) {
+            Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path));
+        }
+        return lib;
     }
 
-    private static String getLoadFromApkPath(String apkPath,
-                                             String[] abiList,
-                                             String nativeLibFileName)
+    /**
+     * @return the directory of the native WebView library with bitness {@param is64bit}.
+     * @hide
+     */
+    @VisibleForTesting
+    public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) {
+        // Primary arch has the same bitness as the library we are looking for.
+        if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir;
+
+        // Secondary arch has the same bitness as the library we are looking for.
+        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
+            return ai.secondaryNativeLibraryDir;
+        }
+
+        return "";
+    }
+
+    /**
+     * @return an object describing a native WebView library given the directory path of that
+     * library, or null if the library couldn't be found.
+     */
+    @Nullable
+    private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai,
+            String nativeLibFileName, String[] abiList, String libDirectory)
+            throws WebViewFactory.MissingWebViewPackageException {
+        if (TextUtils.isEmpty(libDirectory)) return null;
+        String libPath = libDirectory + "/" + nativeLibFileName;
+        File f = new File(libPath);
+        if (f.exists()) {
+            return new WebViewNativeLibrary(libPath, f.length());
+        } else {
+            return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public static class WebViewNativeLibrary {
+        public final String path;
+        public final long size;
+
+        WebViewNativeLibrary(String path, long size) {
+            this.path = path;
+            this.size = size;
+        }
+    }
+
+    private static WebViewNativeLibrary getLoadFromApkPath(String apkPath,
+                                                           String[] abiList,
+                                                           String nativeLibFileName)
             throws WebViewFactory.MissingWebViewPackageException {
         // Search the APK for a native library conforming to a listed ABI.
         try (ZipFile z = new ZipFile(apkPath)) {
@@ -293,13 +335,13 @@
                 ZipEntry e = z.getEntry(entry);
                 if (e != null && e.getMethod() == ZipEntry.STORED) {
                     // Return a path formatted for dlopen() load from APK.
-                    return apkPath + "!/" + entry;
+                    return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize());
                 }
             }
         } catch (IOException e) {
             throw new WebViewFactory.MissingWebViewPackageException(e);
         }
-        return "";
+        return null;
     }
 
     /**
diff --git a/android/widget/AdapterView.java b/android/widget/AdapterView.java
index dd01251..6c19256 100644
--- a/android/widget/AdapterView.java
+++ b/android/widget/AdapterView.java
@@ -202,7 +202,7 @@
      * The last selected position we used when notifying
      */
     int mOldSelectedPosition = INVALID_POSITION;
-    
+
     /**
      * The id of the last selected position we used when notifying
      */
@@ -382,7 +382,7 @@
          * position is different from the previously selected position or if
          * there was no selected item.</p>
          *
-         * Impelmenters can call getItemAtPosition(position) if they need to access the
+         * Implementers can call getItemAtPosition(position) if they need to access the
          * data associated with the selected item.
          *
          * @param parent The AdapterView where the selection happened
@@ -778,8 +778,8 @@
             // We are now GONE, so pending layouts will not be dispatched.
             // Force one here to make sure that the state of the list matches
             // the state of the adapter.
-            if (mDataChanged) {           
-                this.onLayout(false, mLeft, mTop, mRight, mBottom); 
+            if (mDataChanged) {
+                this.onLayout(false, mLeft, mTop, mRight, mBottom);
             }
         } else {
             if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
@@ -1304,4 +1304,4 @@
             structure.setAutofillOptions(options);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index 384f4f8..e6da69d 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -129,7 +129,7 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
-
+import java.util.Map;
 
 /**
  * Helper class used by TextView to handle editable text views.
@@ -163,9 +163,9 @@
     private static final int MENU_ITEM_ORDER_REPLACE = 9;
     private static final int MENU_ITEM_ORDER_AUTOFILL = 10;
     private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
+    private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
     private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
 
-    private static final float MAGNIFIER_ZOOM = 1.25f;
     @IntDef({MagnifierHandleTrigger.SELECTION_START,
             MagnifierHandleTrigger.SELECTION_END,
             MagnifierHandleTrigger.INSERTION})
@@ -3793,6 +3793,7 @@
         private final RectF mSelectionBounds = new RectF();
         private final boolean mHasSelection;
         private final int mHandleHeight;
+        private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>();
 
         public TextActionModeCallback(boolean hasSelection) {
             mHasSelection = hasSelection;
@@ -3820,6 +3821,8 @@
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            mAssistClickHandlers.clear();
+
             mode.setTitle(null);
             mode.setSubtitle(null);
             mode.setTitleOptionalHint(true);
@@ -3903,14 +3906,14 @@
 
             updateSelectAllItem(menu);
             updateReplaceItem(menu);
-            updateAssistMenuItem(menu);
+            updateAssistMenuItems(menu);
         }
 
         @Override
         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
             updateSelectAllItem(menu);
             updateReplaceItem(menu);
-            updateAssistMenuItem(menu);
+            updateAssistMenuItems(menu);
 
             Callback customCallback = getCustomCallback();
             if (customCallback != null) {
@@ -3943,32 +3946,118 @@
             }
         }
 
-        private void updateAssistMenuItem(Menu menu) {
-            menu.removeItem(TextView.ID_ASSIST);
+        private void updateAssistMenuItems(Menu menu) {
+            clearAssistMenuItems(menu);
+            if (!mTextView.isDeviceProvisioned()) {
+                return;
+            }
             final TextClassification textClassification =
                     getSelectionActionModeHelper().getTextClassification();
-            if (canAssist()) {
-                menu.add(TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
-                        textClassification.getLabel())
-                        .setIcon(textClassification.getIcon())
-                        .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-                mMetricsLogger.write(
-                        new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
-                                .setType(MetricsEvent.TYPE_OPEN)
-                                .setSubtype(textClassification.getLogType()));
+            final int count = textClassification != null ? textClassification.getActionCount() : 0;
+            for (int i = 0; i < count; i++) {
+                if (!isValidAssistMenuItem(i)) {
+                    continue;
+                }
+                final int groupId = TextView.ID_ASSIST;
+                final int order = (i == 0)
+                        ? MENU_ITEM_ORDER_ASSIST
+                        : MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i;
+                final int id = (i == 0) ? TextView.ID_ASSIST : Menu.NONE;
+                final int showAsFlag = (i == 0)
+                        ? MenuItem.SHOW_AS_ACTION_ALWAYS
+                        : MenuItem.SHOW_AS_ACTION_NEVER;
+                final MenuItem item = menu.add(
+                        groupId, id, order, textClassification.getLabel(i))
+                        .setIcon(textClassification.getIcon(i))
+                        .setIntent(textClassification.getIntent(i));
+                item.setShowAsAction(showAsFlag);
+                mAssistClickHandlers.put(item, textClassification.getOnClickListener(i));
+                if (id == TextView.ID_ASSIST) {
+                    mMetricsLogger.write(
+                            new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+                                    .setType(MetricsEvent.TYPE_OPEN)
+                                    .setSubtype(textClassification.getLogType()));
+                }
             }
         }
 
-        private boolean canAssist() {
+        private void clearAssistMenuItems(Menu menu) {
+            int i = 0;
+            while (i < menu.size()) {
+                final MenuItem menuItem = menu.getItem(i);
+                if (menuItem.getGroupId() == TextView.ID_ASSIST) {
+                    menu.removeItem(menuItem.getItemId());
+                    continue;
+                }
+                i++;
+            }
+        }
+
+        private boolean isValidAssistMenuItem(int index) {
             final TextClassification textClassification =
                     getSelectionActionModeHelper().getTextClassification();
-            return mTextView.isDeviceProvisioned()
-                    && textClassification != null
-                    && (textClassification.getIcon() != null
-                            || !TextUtils.isEmpty(textClassification.getLabel()))
-                    && (textClassification.getOnClickListener() != null
-                            || (textClassification.getIntent() != null
-                                    && mTextView.getContext().canStartActivityForResult()));
+            if (!mTextView.isDeviceProvisioned() || textClassification == null
+                    || index < 0 || index >= textClassification.getActionCount()) {
+                return false;
+            }
+            final Drawable icon = textClassification.getIcon(index);
+            final CharSequence label = textClassification.getLabel(index);
+            final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
+            final OnClickListener onClick = textClassification.getOnClickListener(index);
+            final Intent intent = textClassification.getIntent(index);
+            final boolean hasAction = onClick != null || isSupportedIntent(intent);
+            return hasUi && hasAction;
+        }
+
+        private boolean isSupportedIntent(Intent intent) {
+            if (intent == null) {
+                return false;
+            }
+            final Context context = mTextView.getContext();
+            final ResolveInfo info = context.getPackageManager().resolveActivity(intent, 0);
+            final boolean samePackage = context.getPackageName().equals(
+                    info.activityInfo.packageName);
+            if (samePackage) {
+                return true;
+            }
+
+            final boolean exported =  info.activityInfo.exported;
+            final boolean requiresPermission = info.activityInfo.permission != null;
+            final boolean hasPermission = !requiresPermission
+                    || context.checkSelfPermission(info.activityInfo.permission)
+                            == PackageManager.PERMISSION_GRANTED;
+            return exported && hasPermission;
+        }
+
+        private boolean onAssistMenuItemClicked(MenuItem assistMenuItem) {
+            Preconditions.checkArgument(assistMenuItem.getGroupId() == TextView.ID_ASSIST);
+
+            final TextClassification textClassification =
+                    getSelectionActionModeHelper().getTextClassification();
+            if (!mTextView.isDeviceProvisioned() || textClassification == null) {
+                // No textClassification result to handle the click. Eat the click.
+                return true;
+            }
+
+            OnClickListener onClickListener = mAssistClickHandlers.get(assistMenuItem);
+            if (onClickListener == null) {
+                final Intent intent = assistMenuItem.getIntent();
+                if (intent != null) {
+                    onClickListener = TextClassification.createStartActivityOnClickListener(
+                            mTextView.getContext(), intent);
+                }
+            }
+            if (onClickListener != null) {
+                onClickListener.onClick(mTextView);
+                stopTextActionMode();
+                if (assistMenuItem.getItemId() == TextView.ID_ASSIST) {
+                    mMetricsLogger.action(
+                            MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
+                            textClassification.getLogType());
+                }
+            }
+            // We tried our best.
+            return true;
         }
 
         @Override
@@ -3982,25 +4071,7 @@
             if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
                 return true;
             }
-            final TextClassification textClassification =
-                    getSelectionActionModeHelper().getTextClassification();
-            if (TextView.ID_ASSIST == item.getItemId() && textClassification != null) {
-                final OnClickListener onClickListener =
-                        textClassification.getOnClickListener();
-                if (onClickListener != null) {
-                    onClickListener.onClick(mTextView);
-                } else {
-                    final Intent intent = textClassification.getIntent();
-                    if (intent != null) {
-                        TextClassification.createStartActivityOnClickListener(
-                                mTextView.getContext(), intent)
-                                .onClick(mTextView);
-                    }
-                }
-                mMetricsLogger.action(
-                        MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
-                        textClassification.getLogType());
-                stopTextActionMode();
+            if (item.getGroupId() == TextView.ID_ASSIST && onAssistMenuItemClicked(item)) {
                 return true;
             }
             return mTextView.onTextContextMenuItem(item.getItemId());
@@ -4029,6 +4100,8 @@
             if (mSelectionModifierCursorController != null) {
                 mSelectionModifierCursorController.hide();
             }
+
+            mAssistClickHandlers.clear();
         }
 
         @Override
@@ -4547,7 +4620,7 @@
                     + mTextView.getTotalPaddingTop() - mTextView.getScrollY();
 
             suspendBlink();
-            mMagnifier.show(xPosInView, yPosInView, MAGNIFIER_ZOOM);
+            mMagnifier.show(xPosInView, yPosInView);
         }
 
         protected final void dismissMagnifier() {
@@ -6560,6 +6633,9 @@
 
         private void loadSupportedActivities() {
             mSupportedActivities.clear();
+            if (!mContext.canStartActivityForResult()) {
+                return;
+            }
             PackageManager packageManager = mTextView.getContext().getPackageManager();
             List<ResolveInfo> unfiltered =
                     packageManager.queryIntentActivities(createProcessTextIntent(), 0);
diff --git a/android/widget/ListPopupWindow.java b/android/widget/ListPopupWindow.java
index 0d67615..adf366a 100644
--- a/android/widget/ListPopupWindow.java
+++ b/android/widget/ListPopupWindow.java
@@ -657,6 +657,7 @@
             mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
                             mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
                             (heightSpec < 0)? -1 : heightSpec);
+            mPopup.getContentView().restoreDefaultFocus();
         } else {
             final int widthSpec;
             if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
@@ -695,6 +696,7 @@
             mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
                     mDropDownVerticalOffset, mDropDownGravity);
             mDropDownList.setSelection(ListView.INVALID_POSITION);
+            mPopup.getContentView().restoreDefaultFocus();
 
             if (!mModal || mDropDownList.isInTouchMode()) {
                 clearListSelection();
diff --git a/android/widget/PopupWindow.java b/android/widget/PopupWindow.java
index 23ebadb..8dc8cab 100644
--- a/android/widget/PopupWindow.java
+++ b/android/widget/PopupWindow.java
@@ -2461,14 +2461,14 @@
             for (int i = 0; i < count; i++) {
                 final View child = getChildAt(i);
                 enterTransition.addTarget(child);
-                child.setVisibility(View.INVISIBLE);
+                child.setTransitionVisibility(View.INVISIBLE);
             }
 
             TransitionManager.beginDelayedTransition(this, enterTransition);
 
             for (int i = 0; i < count; i++) {
                 final View child = getChildAt(i);
-                child.setVisibility(View.VISIBLE);
+                child.setTransitionVisibility(View.VISIBLE);
             }
         }
 
diff --git a/android/widget/RemoteViews.java b/android/widget/RemoteViews.java
index 631f388..e330916 100644
--- a/android/widget/RemoteViews.java
+++ b/android/widget/RemoteViews.java
@@ -81,6 +81,7 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Stack;
 import java.util.concurrent.Executor;
 
@@ -185,6 +186,9 @@
      */
     private boolean mIsWidgetCollectionChild = false;
 
+    /** Class cookies of the Parcel this instance was read from. */
+    private final Map<Class, Object> mClassCookies;
+
     private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler();
 
     private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>();
@@ -1505,10 +1509,10 @@
         }
 
         ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info,
-                int depth) {
+                int depth, Map<Class, Object> classCookies) {
             viewId = parcel.readInt();
             mIndex = parcel.readInt();
-            mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth);
+            mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
@@ -2120,6 +2124,7 @@
         mApplication = application;
         mLayoutId = layoutId;
         mBitmapCache = new BitmapCache();
+        mClassCookies = null;
     }
 
     private boolean hasLandscapeAndPortraitLayouts() {
@@ -2149,6 +2154,9 @@
         mBitmapCache = new BitmapCache();
         configureRemoteViewsAsChild(landscape);
         configureRemoteViewsAsChild(portrait);
+
+        mClassCookies = (portrait.mClassCookies != null)
+                ? portrait.mClassCookies : landscape.mClassCookies;
     }
 
     /**
@@ -2161,15 +2169,16 @@
         mLayoutId = src.mLayoutId;
         mIsWidgetCollectionChild = src.mIsWidgetCollectionChild;
         mReapplyDisallowed = src.mReapplyDisallowed;
+        mClassCookies = src.mClassCookies;
 
         if (src.hasLandscapeAndPortraitLayouts()) {
             mLandscape = new RemoteViews(src.mLandscape);
             mPortrait = new RemoteViews(src.mPortrait);
-
         }
 
         if (src.mActions != null) {
             Parcel p = Parcel.obtain();
+            p.putClassCookies(mClassCookies);
             src.writeActionsToParcel(p);
             p.setDataPosition(0);
             // Since src is already in memory, we do not care about stack overflow as it has
@@ -2189,10 +2198,11 @@
      * @param parcel
      */
     public RemoteViews(Parcel parcel) {
-        this(parcel, null, null, 0);
+        this(parcel, null, null, 0, null);
     }
 
-    private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth) {
+    private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth,
+            Map<Class, Object> classCookies) {
         if (depth > MAX_NESTED_VIEWS
                 && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) {
             throw new IllegalArgumentException("Too many nested views.");
@@ -2204,8 +2214,11 @@
         // We only store a bitmap cache in the root of the RemoteViews.
         if (bitmapCache == null) {
             mBitmapCache = new BitmapCache(parcel);
+            // Store the class cookies such that they are available when we clone this RemoteView.
+            mClassCookies = parcel.copyClassCookies();
         } else {
             setBitmapCache(bitmapCache);
+            mClassCookies = classCookies;
             setNotRoot();
         }
 
@@ -2218,8 +2231,9 @@
             readActionsFromParcel(parcel, depth);
         } else {
             // MODE_HAS_LANDSCAPE_AND_PORTRAIT
-            mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth);
-            mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth);
+            mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
+            mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth,
+                    mClassCookies);
             mApplication = mPortrait.mApplication;
             mLayoutId = mPortrait.getLayoutId();
         }
@@ -2246,7 +2260,8 @@
             case REFLECTION_ACTION_TAG:
                 return new ReflectionAction(parcel);
             case VIEW_GROUP_ACTION_ADD_TAG:
-                return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth);
+                return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth,
+                        mClassCookies);
             case VIEW_GROUP_ACTION_REMOVE_TAG:
                 return new ViewGroupActionRemove(parcel);
             case VIEW_CONTENT_NAVIGATION_TAG:
diff --git a/com/android/car/setupwizardlib/CarSetupWizardLayout.java b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
new file mode 100644
index 0000000..0f5b84b
--- /dev/null
+++ b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
@@ -0,0 +1,143 @@
+/*
+ * 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.car.setupwizardlib;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+
+/**
+ * Custom layout for the Car Setup Wizard.
+ */
+public class CarSetupWizardLayout extends LinearLayout {
+    private View mBackButton;
+
+    private Button mContinueButton;
+
+    private RelativeLayout mHeader;
+
+    public CarSetupWizardLayout(Context context) {
+        this(context, null);
+    }
+
+    public CarSetupWizardLayout(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    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).
+     */
+    public CarSetupWizardLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        TypedArray attrArray = context.getTheme().obtainStyledAttributes(
+                attrs,
+                R.styleable.CarSetupWizardLayout,
+                0, 0);
+
+        init(attrArray);
+
+    }
+
+    /**
+     * Inflates the layout and sets the custom views (e.g. back button, continue button).
+     */
+    void init(TypedArray attrArray) {
+        boolean showBackButton;
+        boolean showContinueButton;
+        String continueButtonText;
+        try {
+            showBackButton = attrArray.getBoolean(
+                    R.styleable.CarSetupWizardLayout_showBackButton, true);
+            showContinueButton = attrArray.getBoolean(
+                    R.styleable.CarSetupWizardLayout_showContinueButton, true);
+            continueButtonText = attrArray.getString(
+                    R.styleable.CarSetupWizardLayout_continueButtonText);
+        } finally {
+            attrArray.recycle();
+        }
+
+        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);
+        }
+
+        // Set the continue button visibility and text based on the custom attributes.
+        mContinueButton = findViewById(R.id.continue_button);
+        if (showContinueButton) {
+            setContinueButtonText(continueButtonText);
+        } else {
+            setContinueButtonVisibility(View.GONE);
+        }
+
+        // TODO: Handle loading bar logic
+    }
+
+    /**
+     * Set the back button visibility to the given 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);
+    }
+
+    /**
+     * Set the back button onClickListener to given listener. Can be null if the listener should
+     * be overridden so no callback is made.
+     */
+    public void setBackButtonListener(@Nullable View.OnClickListener listener) {
+        mBackButton.setOnClickListener(listener);
+    }
+
+    /**
+     * Set the continue button onClickListener to then given listener. Can be null if the listener
+     * should be overridden so no callback is made.
+     */
+    public void setContinueButtonListener(@Nullable View.OnClickListener listener) {
+        mContinueButton.setOnClickListener(listener);
+    }
+}
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
index 60ec8a9..29433f3 100644
--- a/com/android/commands/pm/Pm.java
+++ b/com/android/commands/pm/Pm.java
@@ -116,9 +116,8 @@
     }
 
     public int run(String[] args) throws RemoteException {
-        boolean validCommand = false;
         if (args.length < 1) {
-            return showUsage();
+            return runShellCommand("package", mArgs);
         }
         mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
         mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
@@ -134,18 +133,6 @@
         String op = args[0];
         mNextArg = 1;
 
-        if ("list".equals(op)) {
-            return runList();
-        }
-
-        if ("path".equals(op)) {
-            return runPath();
-        }
-
-        if ("dump".equals(op)) {
-            return runDump();
-        }
-
         if ("install".equals(op)) {
             return runInstall();
         }
@@ -166,134 +153,7 @@
             return runInstallAbandon();
         }
 
-        if ("set-installer".equals(op)) {
-            return runSetInstaller();
-        }
-
-        if ("uninstall".equals(op)) {
-            return runUninstall();
-        }
-
-        if ("clear".equals(op)) {
-            return runClear();
-        }
-
-        if ("enable".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
-        }
-
-        if ("disable".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
-        }
-
-        if ("disable-user".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
-        }
-
-        if ("disable-until-used".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
-        }
-
-        if ("default-state".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
-        }
-
-        if ("hide".equals(op)) {
-            return runSetHiddenSetting(true);
-        }
-
-        if ("unhide".equals(op)) {
-            return runSetHiddenSetting(false);
-        }
-
-        if ("grant".equals(op)) {
-            return runGrantRevokePermission(true);
-        }
-
-        if ("revoke".equals(op)) {
-            return runGrantRevokePermission(false);
-        }
-
-        if ("reset-permissions".equals(op)) {
-            return runResetPermissions();
-        }
-
-        if ("set-permission-enforced".equals(op)) {
-            return runSetPermissionEnforced();
-        }
-
-        if ("set-app-link".equals(op)) {
-            return runSetAppLink();
-        }
-
-        if ("get-app-link".equals(op)) {
-            return runGetAppLink();
-        }
-
-        if ("set-install-location".equals(op)) {
-            return runSetInstallLocation();
-        }
-
-        if ("get-install-location".equals(op)) {
-            return runGetInstallLocation();
-        }
-
-        if ("trim-caches".equals(op)) {
-            return runTrimCaches();
-        }
-
-        if ("create-user".equals(op)) {
-            return runCreateUser();
-        }
-
-        if ("remove-user".equals(op)) {
-            return runRemoveUser();
-        }
-
-        if ("get-max-users".equals(op)) {
-            return runGetMaxUsers();
-        }
-
-        if ("force-dex-opt".equals(op)) {
-            return runForceDexOpt();
-        }
-
-        if ("move-package".equals(op)) {
-            return runMovePackage();
-        }
-
-        if ("move-primary-storage".equals(op)) {
-            return runMovePrimaryStorage();
-        }
-
-        if ("set-user-restriction".equals(op)) {
-            return runSetUserRestriction();
-        }
-
-        try {
-            if (args.length == 1) {
-                if (args[0].equalsIgnoreCase("-l")) {
-                    validCommand = true;
-                    return runShellCommand("package", new String[] { "list", "package" });
-                } else if (args[0].equalsIgnoreCase("-lf")) {
-                    validCommand = true;
-                    return runShellCommand("package", new String[] { "list", "package", "-f" });
-                }
-            } else if (args.length == 2) {
-                if (args[0].equalsIgnoreCase("-p")) {
-                    validCommand = true;
-                    return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM);
-                }
-            }
-            return 1;
-        } finally {
-            if (validCommand == false) {
-                if (op != null) {
-                    System.err.println("Error: unknown command '" + op + "'");
-                }
-                showUsage();
-            }
-        }
+        return runShellCommand("package", mArgs);
     }
 
     static final class MyShellCallback extends ShellCallback {
@@ -704,59 +564,6 @@
         }
     }
 
-    /**
-     * Execute the list sub-command.
-     *
-     * pm list [package | packages]
-     * pm list permission-groups
-     * pm list permissions
-     * pm list features
-     * pm list libraries
-     * pm list instrumentation
-     */
-    private int runList() {
-        final String type = nextArg();
-        if ("users".equals(type)) {
-            return runShellCommand("user", new String[] { "list" });
-        }
-        return runShellCommand("package", mArgs);
-    }
-
-    private int runUninstall() {
-        return runShellCommand("package", mArgs);
-    }
-
-    private int runPath() {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return 1;
-        }
-        return displayPackageFilePath(pkg, userId);
-    }
-
-    private int runDump() {
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return 1;
-        }
-        ActivityManager.dumpPackageStateStatic(FileDescriptor.out, pkg);
-        return 0;
-    }
-
     class LocalPackageInstallObserver extends PackageInstallObserver {
         boolean finished;
         int result;
@@ -779,477 +586,6 @@
         }
     }
 
-    // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
-    private int runSetAppLink() {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextOptionData());
-                if (userId < 0) {
-                    System.err.println("Error: user must be >= 0");
-                    return 1;
-                }
-            } else {
-                System.err.println("Error: unknown option: " + opt);
-                return showUsage();
-            }
-        }
-
-        // Package name to act on; required
-        final String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return showUsage();
-        }
-
-        // State to apply; {always|ask|never|undefined}, required
-        final String modeString = nextArg();
-        if (modeString == null) {
-            System.err.println("Error: no app link state specified.");
-            return showUsage();
-        }
-
-        final int newMode;
-        switch (modeString.toLowerCase()) {
-            case "undefined":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-                break;
-
-            case "always":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-                break;
-
-            case "ask":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-                break;
-
-            case "always-ask":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-                break;
-
-            case "never":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-                break;
-
-            default:
-                System.err.println("Error: unknown app link state '" + modeString + "'");
-                return 1;
-        }
-
-        try {
-            final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId);
-            if (info == null) {
-                System.err.println("Error: package " + pkg + " not found.");
-                return 1;
-            }
-
-            if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
-                System.err.println("Error: package " + pkg + " does not handle web links.");
-                return 1;
-            }
-
-            if (!mPm.updateIntentVerificationStatus(pkg, newMode, userId)) {
-                System.err.println("Error: unable to update app link status for " + pkg);
-                return 1;
-            }
-        } catch (Exception e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-
-        return 0;
-    }
-
-    // pm get-app-link [--user USER_ID] PACKAGE
-    private int runGetAppLink() {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextOptionData());
-                if (userId < 0) {
-                    System.err.println("Error: user must be >= 0");
-                    return 1;
-                }
-            } else {
-                System.err.println("Error: unknown option: " + opt);
-                return showUsage();
-            }
-        }
-
-        // Package name to act on; required
-        final String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return showUsage();
-        }
-
-        try {
-            final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId);
-            if (info == null) {
-                System.err.println("Error: package " + pkg + " not found.");
-                return 1;
-            }
-
-            if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
-                System.err.println("Error: package " + pkg + " does not handle web links.");
-                return 1;
-            }
-
-            System.out.println(linkStateToString(mPm.getIntentVerificationStatus(pkg, userId)));
-        } catch (Exception e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-
-        return 0;
-    }
-
-    private String linkStateToString(int state) {
-        switch (state) {
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
-        }
-        return "Unknown link state: " + state;
-    }
-
-    private int runSetInstallLocation() {
-        int loc;
-
-        String arg = nextArg();
-        if (arg == null) {
-            System.err.println("Error: no install location specified.");
-            return 1;
-        }
-        try {
-            loc = Integer.parseInt(arg);
-        } catch (NumberFormatException e) {
-            System.err.println("Error: install location has to be a number.");
-            return 1;
-        }
-        try {
-            if (!mPm.setInstallLocation(loc)) {
-                System.err.println("Error: install location has to be a number.");
-                return 1;
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private int runGetInstallLocation() {
-        try {
-            int loc = mPm.getInstallLocation();
-            String locStr = "invalid";
-            if (loc == PackageHelper.APP_INSTALL_AUTO) {
-                locStr = "auto";
-            } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
-                locStr = "internal";
-            } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
-                locStr = "external";
-            }
-            System.out.println(loc + "[" + locStr + "]");
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private int runSetInstaller() throws RemoteException {
-        final String targetPackage = nextArg();
-        final String installerPackageName = nextArg();
-
-        if (targetPackage == null || installerPackageName == null) {
-            throw new IllegalArgumentException(
-                    "must provide both target and installer package names");
-        }
-
-        mPm.setInstallerPackageName(targetPackage, installerPackageName);
-        System.out.println("Success");
-        return 0;
-    }
-
-    public int runCreateUser() {
-        String name;
-        int userId = -1;
-        int flags = 0;
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if ("--profileOf".equals(opt)) {
-                String optionData = nextOptionData();
-                if (optionData == null || !isNumber(optionData)) {
-                    System.err.println("Error: no USER_ID specified");
-                    return showUsage();
-                } else {
-                    userId = Integer.parseInt(optionData);
-                }
-            } else if ("--managed".equals(opt)) {
-                flags |= UserInfo.FLAG_MANAGED_PROFILE;
-            } else if ("--restricted".equals(opt)) {
-                flags |= UserInfo.FLAG_RESTRICTED;
-            } else if ("--ephemeral".equals(opt)) {
-                flags |= UserInfo.FLAG_EPHEMERAL;
-            } else if ("--guest".equals(opt)) {
-                flags |= UserInfo.FLAG_GUEST;
-            } else if ("--demo".equals(opt)) {
-                flags |= UserInfo.FLAG_DEMO;
-            } else {
-                System.err.println("Error: unknown option " + opt);
-                return showUsage();
-            }
-        }
-        String arg = nextArg();
-        if (arg == null) {
-            System.err.println("Error: no user name specified.");
-            return 1;
-        }
-        name = arg;
-        try {
-            UserInfo info;
-            if ((flags & UserInfo.FLAG_RESTRICTED) != 0) {
-                // In non-split user mode, userId can only be SYSTEM
-                int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
-                info = mUm.createRestrictedProfile(name, parentUserId);
-                mAm.addSharedAccountsFromParentUser(parentUserId, userId,
-                        (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
-            } else if (userId < 0) {
-                info = mUm.createUser(name, flags);
-            } else {
-                info = mUm.createProfileForUser(name, flags, userId, null);
-            }
-
-            if (info != null) {
-                System.out.println("Success: created user id " + info.id);
-                return 0;
-            } else {
-                System.err.println("Error: couldn't create User.");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    public int runRemoveUser() {
-        int userId;
-        String arg = nextArg();
-        if (arg == null) {
-            System.err.println("Error: no user id specified.");
-            return 1;
-        }
-        try {
-            userId = Integer.parseInt(arg);
-        } catch (NumberFormatException e) {
-            System.err.println("Error: user id '" + arg + "' is not a number.");
-            return 1;
-        }
-        try {
-            if (mUm.removeUser(userId)) {
-                System.out.println("Success: removed user");
-                return 0;
-            } else {
-                System.err.println("Error: couldn't remove user id " + userId);
-                return 1;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    public int runGetMaxUsers() {
-        System.out.println("Maximum supported users: " + UserManager.getMaxSupportedUsers());
-        return 0;
-    }
-
-    public int runForceDexOpt() {
-        final String packageName = nextArg();
-        try {
-            mPm.forceDexOpt(packageName);
-            return 0;
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    public int runMovePackage() {
-        final String packageName = nextArg();
-        String volumeUuid = nextArg();
-        if ("internal".equals(volumeUuid)) {
-            volumeUuid = null;
-        }
-
-        try {
-            final int moveId = mPm.movePackage(packageName, volumeUuid);
-
-            int status = mPm.getMoveStatus(moveId);
-            while (!PackageManager.isMoveStatusFinished(status)) {
-                SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
-                status = mPm.getMoveStatus(moveId);
-            }
-
-            if (status == PackageManager.MOVE_SUCCEEDED) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                System.err.println("Failure [" + status + "]");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    public int runMovePrimaryStorage() {
-        String volumeUuid = nextArg();
-        if ("internal".equals(volumeUuid)) {
-            volumeUuid = null;
-        }
-
-        try {
-            final int moveId = mPm.movePrimaryStorage(volumeUuid);
-
-            int status = mPm.getMoveStatus(moveId);
-            while (!PackageManager.isMoveStatusFinished(status)) {
-                SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
-                status = mPm.getMoveStatus(moveId);
-            }
-
-            if (status == PackageManager.MOVE_SUCCEEDED) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                System.err.println("Failure [" + status + "]");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    public int runSetUserRestriction() {
-        int userId = UserHandle.USER_SYSTEM;
-        String opt = nextOption();
-        if (opt != null && "--user".equals(opt)) {
-            String arg = nextArg();
-            if (arg == null || !isNumber(arg)) {
-                System.err.println("Error: valid userId not specified");
-                return 1;
-            }
-            userId = Integer.parseInt(arg);
-        }
-
-        String restriction = nextArg();
-        String arg = nextArg();
-        boolean value;
-        if ("1".equals(arg)) {
-            value = true;
-        } else if ("0".equals(arg)) {
-            value = false;
-        } else {
-            System.err.println("Error: valid value not specified");
-            return 1;
-        }
-        try {
-            mUm.setUserRestriction(restriction, value, userId);
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            return 1;
-        }
-    }
-
-    static class ClearDataObserver extends IPackageDataObserver.Stub {
-        boolean finished;
-        boolean result;
-
-        @Override
-        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
-            synchronized (this) {
-                finished = true;
-                result = succeeded;
-                notifyAll();
-            }
-        }
-    }
-
-    private int runClear() {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return showUsage();
-        }
-
-        ClearDataObserver obs = new ClearDataObserver();
-        try {
-            ActivityManager.getService().clearApplicationUserData(pkg, obs, userId);
-            synchronized (obs) {
-                while (!obs.finished) {
-                    try {
-                        obs.wait();
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-
-            if (obs.result) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                System.err.println("Failed");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private static String enabledSettingToString(int state) {
-        switch (state) {
-            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
-                return "default";
-            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
-                return "enabled";
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
-                return "disabled";
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
-                return "disabled-user";
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
-                return "disabled-until-used";
-        }
-        return "unknown";
-    }
-
     private static boolean isNumber(String s) {
         try {
             Integer.parseInt(s);
@@ -1259,169 +595,6 @@
         return true;
     }
 
-    private int runSetEnabledSetting(int state) {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package or component specified");
-            return showUsage();
-        }
-        ComponentName cn = ComponentName.unflattenFromString(pkg);
-        if (cn == null) {
-            try {
-                mPm.setApplicationEnabledSetting(pkg, state, 0, userId,
-                        "shell:" + android.os.Process.myUid());
-                System.out.println("Package " + pkg + " new state: "
-                        + enabledSettingToString(
-                        mPm.getApplicationEnabledSetting(pkg, userId)));
-                return 0;
-            } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(PM_NOT_RUNNING_ERR);
-                return 1;
-            }
-        } else {
-            try {
-                mPm.setComponentEnabledSetting(cn, state, 0, userId);
-                System.out.println("Component " + cn.toShortString() + " new state: "
-                        + enabledSettingToString(
-                        mPm.getComponentEnabledSetting(cn, userId)));
-                return 0;
-            } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(PM_NOT_RUNNING_ERR);
-                return 1;
-            }
-        }
-    }
-
-    private int runSetHiddenSetting(boolean state) {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package or component specified");
-            return showUsage();
-        }
-        try {
-            mPm.setApplicationHiddenSettingAsUser(pkg, state, userId);
-            System.out.println("Package " + pkg + " new hidden state: "
-                    + mPm.getApplicationHiddenSettingAsUser(pkg, userId));
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private int runGrantRevokePermission(boolean grant) {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt = null;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextArg());
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return showUsage();
-        }
-        String perm = nextArg();
-        if (perm == null) {
-            System.err.println("Error: no permission specified");
-            return showUsage();
-        }
-
-        try {
-            if (grant) {
-                mPm.grantRuntimePermission(pkg, perm, userId);
-            } else {
-                mPm.revokeRuntimePermission(pkg, perm, userId);
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
-        }
-    }
-
-    private int runResetPermissions() {
-        try {
-            mPm.resetRuntimePermissions();
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
-        }
-    }
-
-    private int runSetPermissionEnforced() {
-        final String permission = nextArg();
-        if (permission == null) {
-            System.err.println("Error: no permission specified");
-            return showUsage();
-        }
-        final String enforcedRaw = nextArg();
-        if (enforcedRaw == null) {
-            System.err.println("Error: no enforcement specified");
-            return showUsage();
-        }
-        final boolean enforced = Boolean.parseBoolean(enforcedRaw);
-        try {
-            mPm.setPermissionEnforced(permission, enforced);
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
-        }
-    }
-
     static class ClearCacheObserver extends IPackageDataObserver.Stub {
         boolean finished;
         boolean result;
@@ -1437,62 +610,17 @@
 
     }
 
-    private int runTrimCaches() {
-        String size = nextArg();
-        if (size == null) {
-            System.err.println("Error: no size specified");
-            return showUsage();
-        }
-        long multiplier = 1;
-        int len = size.length();
-        char c = size.charAt(len - 1);
-        if (c < '0' || c > '9') {
-            if (c == 'K' || c == 'k') {
-                multiplier = 1024L;
-            } else if (c == 'M' || c == 'm') {
-                multiplier = 1024L*1024L;
-            } else if (c == 'G' || c == 'g') {
-                multiplier = 1024L*1024L*1024L;
-            } else {
-                System.err.println("Invalid suffix: " + c);
-                return showUsage();
+    static class ClearDataObserver extends IPackageDataObserver.Stub {
+        boolean finished;
+        boolean result;
+
+        @Override
+        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
+            synchronized (this) {
+                finished = true;
+                result = succeeded;
+                notifyAll();
             }
-            size = size.substring(0, len-1);
-        }
-        long sizeVal;
-        try {
-            sizeVal = Long.parseLong(size) * multiplier;
-        } catch (NumberFormatException e) {
-            System.err.println("Error: expected number at: " + size);
-            return showUsage();
-        }
-        String volumeUuid = nextArg();
-        if ("internal".equals(volumeUuid)) {
-            volumeUuid = null;
-        }
-        ClearDataObserver obs = new ClearDataObserver();
-        try {
-            mPm.freeStorageAndNotify(volumeUuid, sizeVal,
-                    StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs);
-            synchronized (obs) {
-                while (!obs.finished) {
-                    try {
-                        obs.wait();
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
         }
     }
 
diff --git a/com/android/ex/photo/ActionBarWrapper.java b/com/android/ex/photo/ActionBarWrapper.java
index ae62197..6d4d4d2 100644
--- a/com/android/ex/photo/ActionBarWrapper.java
+++ b/com/android/ex/photo/ActionBarWrapper.java
@@ -1,8 +1,7 @@
 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 a5c4a43..7b53918 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.v7.app.AppCompatActivity;
+import android.support.v4.app.FragmentActivity;
 import android.view.Menu;
 import android.view.MenuItem;
 
 /**
  * Activity to view the contents of an album.
  */
-public class PhotoViewActivity extends AppCompatActivity
+public class PhotoViewActivity extends FragmentActivity
         implements PhotoViewController.ActivityInterface {
 
     private PhotoViewController mController;
@@ -41,7 +41,7 @@
         mController.onCreate(savedInstanceState);
     }
 
-    protected PhotoViewController createController() {
+    public PhotoViewController createController() {
         return new PhotoViewController(this);
     }
 
@@ -122,7 +122,7 @@
     @Override
     public ActionBarInterface getActionBarInterface() {
         if (mActionBar == null) {
-            mActionBar = new ActionBarWrapper(getSupportActionBar());
+            mActionBar = new ActionBarWrapper(getActionBar());
         }
         return mActionBar;
     }
diff --git a/com/android/internal/app/NightDisplayController.java b/com/android/internal/app/NightDisplayController.java
index 7a1383c..b2053c0 100644
--- a/com/android/internal/app/NightDisplayController.java
+++ b/com/android/internal/app/NightDisplayController.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.Settings.Secure;
+import android.provider.Settings.System;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -76,6 +77,29 @@
      */
     public static final int AUTO_MODE_TWILIGHT = 2;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED })
+    public @interface ColorMode {}
+
+    /**
+     * Color mode with natural colors.
+     *
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_NATURAL = 0;
+    /**
+     * Color mode with boosted colors.
+     *
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_BOOSTED = 1;
+    /**
+     * Color mode with saturated colors.
+     *
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_SATURATED = 2;
+
     private final Context mContext;
     private final int mUserId;
 
@@ -306,6 +330,31 @@
     }
 
     /**
+     * Get the current color mode.
+     */
+    public int getColorMode() {
+        final int colorMode = System.getIntForUser(mContext.getContentResolver(),
+            System.DISPLAY_COLOR_MODE, COLOR_MODE_BOOSTED, mUserId);
+        if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
+            return COLOR_MODE_BOOSTED;
+        }
+        return colorMode;
+    }
+
+    /**
+     * Set the current color mode.
+     *
+     * @param colorMode the color mode
+     */
+    public void setColorMode(@ColorMode int colorMode) {
+        if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
+            throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
+        }
+        System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode,
+                mUserId);
+    }
+
+    /**
      * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated.
      */
     public int getMinimumColorTemperature() {
@@ -351,6 +400,9 @@
                 case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
                     mCallback.onColorTemperatureChanged(getColorTemperature());
                     break;
+                case System.DISPLAY_COLOR_MODE:
+                    mCallback.onDisplayColorModeChanged(getColorMode());
+                    break;
             }
         }
     }
@@ -379,6 +431,8 @@
                         false /* notifyForDescendants */, mContentObserver, mUserId);
                 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
                         false /* notifyForDescendants */, mContentObserver, mUserId);
+                cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE),
+                        false /* notifyForDecendants */, mContentObserver, mUserId);
             }
         }
     }
@@ -425,5 +479,12 @@
          * @param colorTemperature the color temperature to tint the screen
          */
         default void onColorTemperatureChanged(int colorTemperature) {}
+
+        /**
+         * Callback invoked when the color mode changes.
+         *
+         * @param displayColorMode the color mode
+         */
+        default void onDisplayColorModeChanged(int displayColorMode) {}
     }
 }
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index 9cc1959..f0d05da 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -4055,6 +4055,7 @@
             long deltaUptime = uptimeMs - mLastWakeupUptimeMs;
             SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
             timer.add(deltaUptime * 1000, 1); // time in in microseconds
+            StatsLog.write(StatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason, deltaUptime * 1000);
             mLastWakeupReason = null;
         }
     }
@@ -4633,6 +4634,7 @@
                 if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
                         + Integer.toHexString(mHistoryCur.states));
                 newHistory = true;
+                StatsLog.write(StatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
             } else {
                 stopAllPhoneSignalStrengthTimersLocked(-1);
             }
@@ -5188,6 +5190,7 @@
             if (strengthBin >= 0) {
                 if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
                     mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
+                    StatsLog.write(StatsLog.WIFI_SIGNAL_STRENGTH_CHANGED, strengthBin);
                 }
                 mHistoryCur.states2 =
                         (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
@@ -6053,6 +6056,8 @@
                             mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase);
                 }
                 mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 1);
             }
         }
 
@@ -6061,6 +6066,10 @@
             if (mFullWifiLockOut) {
                 mFullWifiLockOut = false;
                 mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mFullWifiLockTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
@@ -6074,6 +6083,8 @@
                             mOnBatteryBackgroundTimeBase);
                 }
                 mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 1);
             }
         }
 
@@ -6082,6 +6093,10 @@
             if (mWifiScanStarted) {
                 mWifiScanStarted = false;
                 mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mWifiScanTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
@@ -8876,6 +8891,8 @@
             Wakelock wl = mWakelockStats.startObject(name);
             if (wl != null) {
                 getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Hopefully use a worksource instead of a uid (so move elsewhere)
+                StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 1);
             }
             if (type == WAKE_TYPE_PARTIAL) {
                 createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs);
@@ -8893,7 +8910,12 @@
         public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
             Wakelock wl = mWakelockStats.stopObject(name);
             if (wl != null) {
-                getWakelockTimerLocked(wl, type).stopRunningLocked(elapsedRealtimeMs);
+                StopwatchTimer wlt = getWakelockTimerLocked(wl, type);
+                wlt.stopRunningLocked(elapsedRealtimeMs);
+                if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 0);
+                }
             }
             if (type == WAKE_TYPE_PARTIAL) {
                 if (mAggregatedPartialWakelockTimer != null) {
@@ -11156,11 +11178,15 @@
     // This should probably be exposed in the API, though it's not critical
     public static final int BATTERY_PLUGGED_NONE = 0;
 
-    public void setBatteryStateLocked(int status, int health, int plugType, int level,
-            int temp, int volt, int chargeUAh, int chargeFullUAh) {
+    public void setBatteryStateLocked(final int status, final int health, final int plugType,
+            final int level, /* not final */ int temp, final int volt, final int chargeUAh,
+            final int chargeFullUAh) {
         // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
         temp = Math.max(0, temp);
 
+        reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null,
+                status, plugType, level, temp);
+
         final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
         final long uptime = mClocks.uptimeMillis();
         final long elapsedRealtime = mClocks.elapsedRealtime();
@@ -11337,6 +11363,24 @@
         mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh);
     }
 
+    // Inform StatsLog of setBatteryState changes.
+    // If this is the first reporting, pass in recentPast == null.
+    private void reportChangesToStatsLog(HistoryItem recentPast,
+            final int status, final int plugType, final int level, final int temp) {
+
+        if (recentPast == null || recentPast.batteryStatus != status) {
+            StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, status);
+        }
+        if (recentPast == null || recentPast.batteryPlugType != plugType) {
+            StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, plugType);
+        }
+        if (recentPast == null || recentPast.batteryLevel != level) {
+            StatsLog.write(StatsLog.BATTERY_LEVEL_CHANGED, level);
+        }
+        // Let's just always print the temperature, regardless of whether it changed.
+        StatsLog.write(StatsLog.DEVICE_TEMPERATURE_REPORTED, temp);
+    }
+
     public long getAwakeTimeBattery() {
         return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT);
     }
diff --git a/com/android/internal/os/BinderInternal.java b/com/android/internal/os/BinderInternal.java
index ea4575a..5bddd2f 100644
--- a/com/android/internal/os/BinderInternal.java
+++ b/com/android/internal/os/BinderInternal.java
@@ -16,9 +16,15 @@
 
 package com.android.internal.os;
 
+import android.annotation.NonNull;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.SystemClock;
 import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.Preconditions;
 
 import dalvik.system.VMRuntime;
 
@@ -31,11 +37,14 @@
  * @see IBinder
  */
 public class BinderInternal {
+    private static final String TAG = "BinderInternal";
     static WeakReference<GcWatcher> sGcWatcher
             = new WeakReference<GcWatcher>(new GcWatcher());
     static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
     static Runnable[] sTmpWatchers = new Runnable[1];
     static long sLastGcTime;
+    static final BinderProxyLimitListenerDelegate sBinderProxyLimitListenerDelegate =
+            new BinderProxyLimitListenerDelegate();
 
     static final class GcWatcher {
         @Override
@@ -106,4 +115,96 @@
     static void forceBinderGc() {
         forceGc("Binder");
     }
+
+    /**
+     * Enable/disable Binder Proxy Instance Counting by Uid. While enabled, the set callback will
+     * be called if this process holds too many Binder Proxies on behalf of a Uid.
+     * @param enabled true to enable counting, false to disable
+     */
+    public static final native void nSetBinderProxyCountEnabled(boolean enabled);
+
+    /**
+     * Get the current number of Binder Proxies held for each uid.
+     * @return SparseIntArray mapping uids to the number of Binder Proxies currently held
+     */
+    public static final native SparseIntArray nGetBinderProxyPerUidCounts();
+
+    /**
+     * Get the current number of Binder Proxies held for an individual uid.
+     * @param uid Requested uid for Binder Proxy count
+     * @return int with the number of Binder proxies held for a uid
+     */
+    public static final native int nGetBinderProxyCount(int uid);
+
+    /**
+     * Set the Binder Proxy watermarks. Default high watermark = 2500. Default low watermark = 2000
+     * @param high  The limit at which the BinderProxyListener callback will be called.
+     * @param low   The threshold a binder count must drop below before the callback
+     *              can be called again. (This is to avoid many repeated calls to the
+     *              callback in a brief period of time)
+     */
+    public static final native void nSetBinderProxyCountWatermarks(int high, int low);
+
+    /**
+     * Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will
+     * be called with the uid of the app causing too many Binder Proxies
+     */
+    public interface BinderProxyLimitListener {
+        public void onLimitReached(int uid);
+    }
+
+    /**
+     * Callback used by native code to trigger a callback in java code. The callback will be
+     * triggered when too many binder proxies from a uid hits the allowed limit.
+     * @param uid The uid of the bad behaving app sending too many binders
+     */
+    public static void binderProxyLimitCallbackFromNative(int uid) {
+       sBinderProxyLimitListenerDelegate.notifyClient(uid);
+    }
+
+    /**
+     * Set a callback to be triggered when a uid's Binder Proxy limit is reached for this process.
+     * @param listener OnLimitReached of listener will be called in the thread provided by handler
+     * @param handler must not be null, callback will be posted through the handler;
+     *
+     */
+    public static void setBinderProxyCountCallback(BinderProxyLimitListener listener,
+            @NonNull Handler handler) {
+        Preconditions.checkNotNull(handler,
+                "Must provide NonNull Handler to setBinderProxyCountCallback when setting "
+                        + "BinderProxyLimitListener");
+        sBinderProxyLimitListenerDelegate.setListener(listener, handler);
+    }
+
+    /**
+     * Clear the Binder Proxy callback
+     */
+    public static void clearBinderProxyCountCallback() {
+        sBinderProxyLimitListenerDelegate.setListener(null, null);
+    }
+
+    static private class BinderProxyLimitListenerDelegate {
+        private BinderProxyLimitListener mBinderProxyLimitListener;
+        private Handler mHandler;
+
+        void setListener(BinderProxyLimitListener listener, Handler handler) {
+            synchronized (this) {
+                mBinderProxyLimitListener = listener;
+                mHandler = handler;
+            }
+        }
+
+        void notifyClient(final int uid) {
+            synchronized (this) {
+                if (mBinderProxyLimitListener != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mBinderProxyLimitListener.onLimitReached(uid);
+                        }
+                    });
+                }
+            }
+        }
+    }
 }
diff --git a/com/android/internal/telephony/GsmCdmaConnection.java b/com/android/internal/telephony/GsmCdmaConnection.java
index 79373ae..afef78f 100644
--- a/com/android/internal/telephony/GsmCdmaConnection.java
+++ b/com/android/internal/telephony/GsmCdmaConnection.java
@@ -501,6 +501,9 @@
             case CallFailCause.CDMA_ACCESS_BLOCKED:
                 return DisconnectCause.CDMA_ACCESS_BLOCKED;
 
+            case CallFailCause.NORMAL_UNSPECIFIED:
+                return DisconnectCause.NORMAL_UNSPECIFIED;
+
             case CallFailCause.ERROR_UNSPECIFIED:
             case CallFailCause.NORMAL_CLEARING:
             default:
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index 16f816f..cf52097 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -56,7 +56,6 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.VoLteServiceState;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.ims.ImsCall;
 import com.android.ims.ImsConfig;
@@ -277,8 +276,7 @@
     public SmsUsageMonitor mSmsUsageMonitor;
     protected AtomicReference<UiccCardApplication> mUiccApplication =
             new AtomicReference<UiccCardApplication>();
-
-    private TelephonyTester mTelephonyTester;
+    TelephonyTester mTelephonyTester;
     private String mName;
     private final String mActionDetached;
     private final String mActionAttached;
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index 3aafdfb..e0f94de 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -2678,6 +2678,10 @@
         useDataRegStateForDataOnlyDevices();
         resetServiceStateInIwlanMode();
 
+        if (Build.IS_DEBUGGABLE && mPhone.mTelephonyTester != null) {
+            mPhone.mTelephonyTester.overrideServiceState(mNewSS);
+        }
+
         if (DBG) {
             log("Poll ServiceState done: "
                     + " oldSS=[" + mSS + "] newSS=[" + mNewSS + "]"
diff --git a/com/android/internal/telephony/TelephonyTester.java b/com/android/internal/telephony/TelephonyTester.java
index 0e80937..b59fcb6 100644
--- a/com/android/internal/telephony/TelephonyTester.java
+++ b/com/android/internal/telephony/TelephonyTester.java
@@ -50,6 +50,10 @@
  * adb shell am broadcast -a com.android.internal.telephony.{name}.action_attached
  * adb shell am broadcast -a com.android.internal.telephony.TestConferenceEventPackage -e filename
  *      test_filename.xml
+ * adb shell am broadcast -a com.android.internal.telephony.TestServiceState --ei data_rat 10 --ei
+ *      data_roaming_type 3
+ * adb shell am broadcast -a com.android.internal.telephony.TestServiceState --es action reset
+ *
  */
 public class TelephonyTester {
     private static final String LOG_TAG = "TelephonyTester";
@@ -99,8 +103,24 @@
 
     private static final String EXTRA_CODE = "code";
 
+
+    private static final String ACTION_TEST_SERVICE_STATE =
+            "com.android.internal.telephony.TestServiceState";
+
+    private static final String EXTRA_ACTION = "action";
+    private static final String EXTRA_VOICE_RAT = "voice_rat";
+    private static final String EXTRA_DATA_RAT = "data_rat";
+    private static final String EXTRA_VOICE_REG_STATE = "voice_reg_state";
+    private static final String EXTRA_DATA_REG_STATE = "data_reg_state";
+    private static final String EXTRA_VOICE_ROAMING_TYPE = "voice_roaming_type";
+    private static final String EXTRA_DATA_ROAMING_TYPE = "data_roaming_type";
+
+    private static final String ACTION_RESET = "reset";
+
     private static List<ImsExternalCallState> mImsExternalCallStates = null;
 
+    private Intent mServiceStateTestIntent;
+
     private Phone mPhone;
 
     // The static intent receiver one for all instances and we assume this
@@ -133,6 +153,13 @@
                 } else if (action.equals(ACTION_TEST_SUPP_SRVC_NOTIFICATION)) {
                     log("handle supp service notification test intent");
                     sendTestSuppServiceNotification(intent);
+                } else if (action.equals(ACTION_TEST_SERVICE_STATE)) {
+                    log("handle test service state changed intent");
+                    // Trigger the service state update. The replacement will be done in
+                    // overrideServiceState().
+                    mServiceStateTestIntent = intent;
+                    mPhone.getServiceStateTracker().sendEmptyMessage(
+                            ServiceStateTracker.EVENT_NETWORK_STATE_CHANGED);
                 } else {
                     if (DBG) log("onReceive: unknown action=" + action);
                 }
@@ -162,6 +189,9 @@
                 filter.addAction(ACTION_TEST_HANDOVER_FAIL);
                 filter.addAction(ACTION_TEST_SUPP_SRVC_NOTIFICATION);
                 mImsExternalCallStates = new ArrayList<ImsExternalCallState>();
+            } else {
+                filter.addAction(ACTION_TEST_SERVICE_STATE);
+                log("register for intent action=" + ACTION_TEST_SERVICE_STATE);
             }
 
             phone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone.getHandler());
@@ -298,4 +328,43 @@
             imsPhone.notifySuppSvcNotification(suppServiceNotification);
         }
     }
-}
+
+    void overrideServiceState(ServiceState ss) {
+        if (mServiceStateTestIntent == null || ss == null) return;
+        if (mServiceStateTestIntent.hasExtra(EXTRA_ACTION)
+                && ACTION_RESET.equals(mServiceStateTestIntent.getStringExtra(EXTRA_ACTION))) {
+            log("Service state override reset");
+            return;
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_REG_STATE)) {
+            ss.setVoiceRegState(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_REG_STATE,
+                    ServiceState.RIL_REG_STATE_UNKNOWN));
+            log("Override voice reg state with " + ss.getVoiceRegState());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_REG_STATE)) {
+            ss.setDataRegState(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE,
+                    ServiceState.RIL_REG_STATE_UNKNOWN));
+            log("Override data reg state with " + ss.getDataRegState());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_RAT)) {
+            ss.setRilVoiceRadioTechnology(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_RAT,
+                    ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN));
+            log("Override voice rat with " + ss.getRilVoiceRadioTechnology());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_RAT)) {
+            ss.setRilDataRadioTechnology(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_RAT,
+                    ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN));
+            log("Override data rat with " + ss.getRilDataRadioTechnology());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_ROAMING_TYPE)) {
+            ss.setVoiceRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_ROAMING_TYPE,
+                    ServiceState.ROAMING_TYPE_UNKNOWN));
+            log("Override voice roaming type with " + ss.getVoiceRoamingType());
+        }
+        if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_ROAMING_TYPE)) {
+            ss.setDataRoamingType(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_ROAMING_TYPE,
+                    ServiceState.ROAMING_TYPE_UNKNOWN));
+            log("Override data roaming type with " + ss.getDataRoamingType());
+        }
+    }
+}
\ No newline at end of file
diff --git a/com/android/internal/telephony/dataconnection/DataConnection.java b/com/android/internal/telephony/dataconnection/DataConnection.java
index 33d0b1d..14b2c62 100644
--- a/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -35,6 +35,7 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.LocalLog;
 import android.util.Pair;
 import android.util.TimeUtils;
 
@@ -50,6 +51,7 @@
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -168,6 +170,7 @@
     private int mDataRegState = Integer.MAX_VALUE;
     private NetworkInfo mNetworkInfo;
     private NetworkAgent mNetworkAgent;
+    private LocalLog mLocalLog = new LocalLog(50);
 
     int mTag;
     public int mCid;
@@ -1835,9 +1838,14 @@
 
 
     private class DcNetworkAgent extends NetworkAgent {
+
+        private NetworkCapabilities mNetworkCapabilities;
+
         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);
+            mNetworkCapabilities = nc;
         }
 
         @Override
@@ -1880,6 +1888,21 @@
                 msg.sendToTarget();
             }
         }
+
+        @Override
+        public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
+            if (!networkCapabilities.equals(mNetworkCapabilities)) {
+                String logStr = "Changed from " + mNetworkCapabilities + " to "
+                        + networkCapabilities + ", Data RAT="
+                        + mPhone.getServiceState().getRilDataRadioTechnology()
+                        + ", DUN APN=\"" + mDct.fetchDunApn() + "\""
+                        + ", mApnSetting=" + mApnSetting;
+                mLocalLog.log(logStr);
+                log(logStr);
+                mNetworkCapabilities = networkCapabilities;
+            }
+            super.sendNetworkCapabilities(networkCapabilities);
+        }
     }
 
     // ******* "public" interface
@@ -2088,34 +2111,39 @@
      * @param args
      */
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
         pw.print("DataConnection ");
         super.dump(fd, pw, args);
-        pw.println(" mApnContexts.size=" + mApnContexts.size());
-        pw.println(" mApnContexts=" + mApnContexts);
         pw.flush();
-        pw.println(" mDataConnectionTracker=" + mDct);
-        pw.println(" mApnSetting=" + mApnSetting);
-        pw.println(" mTag=" + mTag);
-        pw.println(" mCid=" + mCid);
-        pw.println(" mConnectionParams=" + mConnectionParams);
-        pw.println(" mDisconnectParams=" + mDisconnectParams);
-        pw.println(" mDcFailCause=" + mDcFailCause);
+        pw.increaseIndent();
+        pw.println("mApnContexts.size=" + mApnContexts.size());
+        pw.println("mApnContexts=" + mApnContexts);
+        pw.println("mDataConnectionTracker=" + mDct);
+        pw.println("mApnSetting=" + mApnSetting);
+        pw.println("mTag=" + mTag);
+        pw.println("mCid=" + mCid);
+        pw.println("mConnectionParams=" + mConnectionParams);
+        pw.println("mDisconnectParams=" + mDisconnectParams);
+        pw.println("mDcFailCause=" + mDcFailCause);
+        pw.println("mPhone=" + mPhone);
+        pw.println("mLinkProperties=" + mLinkProperties);
         pw.flush();
-        pw.println(" mPhone=" + mPhone);
-        pw.flush();
-        pw.println(" mLinkProperties=" + mLinkProperties);
-        pw.flush();
-        pw.println(" mDataRegState=" + mDataRegState);
-        pw.println(" mRilRat=" + mRilRat);
-        pw.println(" mNetworkCapabilities=" + getNetworkCapabilities());
-        pw.println(" mCreateTime=" + TimeUtils.logTimeOfDay(mCreateTime));
-        pw.println(" mLastFailTime=" + TimeUtils.logTimeOfDay(mLastFailTime));
-        pw.println(" mLastFailCause=" + mLastFailCause);
-        pw.flush();
-        pw.println(" mUserData=" + mUserData);
-        pw.println(" mInstanceNumber=" + mInstanceNumber);
-        pw.println(" mAc=" + mAc);
+        pw.println("mDataRegState=" + mDataRegState);
+        pw.println("mRilRat=" + mRilRat);
+        pw.println("mNetworkCapabilities=" + getNetworkCapabilities());
+        pw.println("mCreateTime=" + TimeUtils.logTimeOfDay(mCreateTime));
+        pw.println("mLastFailTime=" + TimeUtils.logTimeOfDay(mLastFailTime));
+        pw.println("mLastFailCause=" + mLastFailCause);
+        pw.println("mUserData=" + mUserData);
+        pw.println("mInstanceNumber=" + mInstanceNumber);
+        pw.println("mAc=" + mAc);
+        pw.println("Network capabilities changed history:");
+        pw.increaseIndent();
+        mLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+        pw.decreaseIndent();
+        pw.println();
         pw.flush();
     }
 }
diff --git a/com/android/internal/telephony/gsm/GsmMmiCode.java b/com/android/internal/telephony/gsm/GsmMmiCode.java
index 54c4d29..b9a07f9 100644
--- a/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -32,7 +32,9 @@
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.ResultReceiver;
+import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.Rlog;
 import android.text.BidiFormatter;
@@ -51,6 +53,7 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.UiccCardApplication;
+import com.android.internal.util.ArrayUtils;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -234,16 +237,20 @@
             ret.mPwd = makeEmptyNull(m.group(MATCH_GROUP_PWD_CONFIRM));
             ret.mDialingNumber = makeEmptyNull(m.group(MATCH_GROUP_DIALING_NUMBER));
             ret.mCallbackReceiver = wrappedCallback;
-            // According to TS 22.030 6.5.2 "Structure of the MMI",
-            // the dialing number should not ending with #.
-            // The dialing number ending # is treated as unique USSD,
-            // eg, *400#16 digit number# to recharge the prepaid card
-            // in India operator(Mumbai MTNL)
+
             if(ret.mDialingNumber != null &&
                     ret.mDialingNumber.endsWith("#") &&
                     dialString.endsWith("#")){
+                // According to TS 22.030 6.5.2 "Structure of the MMI",
+                // the dialing number should not ending with #.
+                // The dialing number ending # is treated as unique USSD,
+                // eg, *400#16 digit number# to recharge the prepaid card
+                // in India operator(Mumbai MTNL)
                 ret = new GsmMmiCode(phone, app);
                 ret.mPoundString = dialString;
+            } else if (ret.isFacToDial()) {
+                // This is a FAC (feature access code) to dial as a normal call.
+                ret = null;
             }
         } else if (dialString.endsWith("#")) {
             // TS 22.030 sec 6.5.3.2
@@ -795,6 +802,30 @@
         return CommandsInterface.CLIR_DEFAULT;
     }
 
+    /**
+     * Returns true if the Service Code is FAC to dial as a normal call.
+     *
+     * FAC stands for feature access code and it is special patterns of characters
+     * to invoke certain features.
+     */
+    private boolean isFacToDial() {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
+        if (b != null) {
+            String[] dialFacList = b.getStringArray(CarrierConfigManager
+                    .KEY_FEATURE_ACCESS_CODES_STRING_ARRAY);
+            if (!ArrayUtils.isEmpty(dialFacList)) {
+                for (String fac : dialFacList) {
+                    if (fac.equals(mSc)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
     boolean isActivate() {
         return mAction != null && mAction.equals(ACTION_ACTIVATE);
     }
diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java
index 51849ff..bb2c34f 100644
--- a/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -1469,6 +1469,7 @@
 
         // send an Intent
         sendEmergencyCallbackModeChange();
+        ((GsmCdmaPhone) mDefaultPhone).notifyEmergencyCallRegistrants(false);
     }
 
     /**
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneBase.java b/com/android/internal/telephony/imsphone/ImsPhoneBase.java
index 87b96d8..c165b03 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneBase.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneBase.java
@@ -496,9 +496,6 @@
         return false;
     }
 
-    public void saveClirSetting(int commandInterfaceCLIRMode) {
-    }
-
     @Override
     public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager(){
         return null;
diff --git a/com/android/internal/telephony/uicc/PlmnActRecord.java b/com/android/internal/telephony/uicc/PlmnActRecord.java
index 2218280..ea87c8d 100644
--- a/com/android/internal/telephony/uicc/PlmnActRecord.java
+++ b/com/android/internal/telephony/uicc/PlmnActRecord.java
@@ -64,7 +64,8 @@
     public PlmnActRecord(byte[] bytes, int offset) {
         if (VDBG) Rlog.v(LOG_TAG, "Creating PlmnActRecord " + offset);
         this.plmn = IccUtils.bcdPlmnToString(bytes, offset);
-        this.accessTechs = ((int) bytes[offset + 3] << 8) | bytes[offset + 4];
+        this.accessTechs = (Byte.toUnsignedInt(bytes[offset + 3]) << 8)
+                        | Byte.toUnsignedInt(bytes[offset + 4]);
     }
 
     private PlmnActRecord(String plmn, int accessTechs) {
diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java
index dad1ee2..8aed931 100644
--- a/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/com/android/internal/telephony/uicc/SIMRecords.java
@@ -28,11 +28,13 @@
 import android.telephony.Rlog;
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.gsm.SimTlv;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
@@ -1645,6 +1647,33 @@
         } else {
             setSpnFromConfig(getOperatorNumeric());
         }
+
+        /* update display name with carrier override */
+        setDisplayName();
+    }
+
+    private void setDisplayName() {
+        SubscriptionManager subManager = SubscriptionManager.from(mContext);
+        int[] subId = subManager.getSubId(mParentApp.getPhoneId());
+
+        if ((subId == null) || subId.length <= 0) {
+            log("subId not valid for Phone " + mParentApp.getPhoneId());
+            return;
+        }
+
+        SubscriptionInfo subInfo = subManager.getActiveSubscriptionInfo(subId[0]);
+        if (subInfo != null && subInfo.getNameSource()
+                    != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
+            CharSequence oldSubName = subInfo.getDisplayName();
+            String newCarrierName = mTelephonyManager.getSimOperatorName(subId[0]);
+
+            if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
+                log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName);
+                SubscriptionController.getInstance().setDisplayName(newCarrierName, subId[0]);
+            }
+        } else {
+            log("SUB[" + mParentApp.getPhoneId() + "] " + subId[0] + " SubInfo not created yet");
+        }
     }
 
     private void setSpnFromConfig(String carrier) {
@@ -2072,10 +2101,14 @@
             return null;
         }
         int numPlmns = data.length / packedBcdPlmnLenBytes;
-        String[] ret = new String[numPlmns];
+        int numValidPlmns = 0;
+        String[] parsed = new String[numPlmns];
         for (int i = 0; i < numPlmns; i++) {
-            ret[i] = IccUtils.bcdPlmnToString(data, i * packedBcdPlmnLenBytes);
+            parsed[numValidPlmns] = IccUtils.bcdPlmnToString(data, i * packedBcdPlmnLenBytes);
+            // we count the valid (non empty) records and only increment if valid
+            if (!TextUtils.isEmpty(parsed[numValidPlmns])) numValidPlmns++;
         }
+        String[] ret = Arrays.copyOf(parsed, numValidPlmns);
         if (VDBG) logv(description + " PLMNs: " + Arrays.toString(ret));
         return ret;
     }
diff --git a/com/android/internal/util/LocalLog.java b/com/android/internal/util/LocalLog.java
index f0e6171..8edb739 100644
--- a/com/android/internal/util/LocalLog.java
+++ b/com/android/internal/util/LocalLog.java
@@ -20,6 +20,7 @@
 import java.util.ArrayList;
 
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 /**
  * Helper class for logging serious issues, which also keeps a small
@@ -63,4 +64,16 @@
             return true;
         }
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        synchronized (mLines) {
+            for (int i = 0; i < mLines.size(); ++i) {
+                proto.write(LocalLogProto.LINES, mLines.get(i));
+            }
+        }
+
+        proto.end(token);
+    }
 }
diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java
index 6d54d7b..f6741c3 100644
--- a/com/android/internal/widget/Magnifier.java
+++ b/com/android/internal/widget/Magnifier.java
@@ -63,7 +63,7 @@
     // the copy is finished.
     private final Handler mPixelCopyHandler = Handler.getMain();
     // Current magnification scale.
-    private float mScale;
+    private final float mZoomScale;
     // Timer used to schedule the copy task.
     private Timer mTimer;
 
@@ -76,11 +76,12 @@
     public Magnifier(@NonNull View view) {
         mView = Preconditions.checkNotNull(view);
         final Context context = mView.getContext();
+        final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
         final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
         content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
         mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
         mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
-        final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
+        mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale);
 
         mWindow = new PopupWindow(context);
         mWindow.setContentView(content);
@@ -90,7 +91,9 @@
         mWindow.setTouchable(false);
         mWindow.setBackgroundDrawable(null);
 
-        mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888);
+        final int bitmapWidth = (int) (mWindowWidth / mZoomScale);
+        final int bitmapHeight = (int) (mWindowHeight / mZoomScale);
+        mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
         getImageView().setImageBitmap(mBitmap);
     }
 
@@ -101,20 +104,8 @@
      *        to the view. The lower end is clamped to 0
      * @param yPosInView vertical coordinate of the center point of the magnifier source
      *        relative to the view. The lower end is clamped to 0
-     * @param scale the scale at which the magnifier zooms on the source content. The
-     *        lower end is clamped to 1 and the higher end to 4
      */
-    public void show(@FloatRange(from=0) float xPosInView,
-            @FloatRange(from=0) float yPosInView,
-            @FloatRange(from=1, to=4) float scale) {
-        if (scale > 4) {
-            scale = 4;
-        }
-
-        if (scale < 1) {
-            scale = 1;
-        }
-
+    public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) {
         if (xPosInView < 0) {
             xPosInView = 0;
         }
@@ -123,10 +114,6 @@
             yPosInView = 0;
         }
 
-        if (mScale != scale) {
-            resizeBitmap(scale);
-        }
-        mScale = scale;
         configureCoordinates(xPosInView, yPosInView);
 
         if (mTimer == null) {
@@ -175,11 +162,11 @@
         return mWindowWidth;
     }
 
-    private void resizeBitmap(float scale) {
-        final int bitmapWidth = (int) (mWindowWidth / scale);
-        final int bitmapHeight = (int) (mWindowHeight / scale);
-        mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
-        getImageView().setImageBitmap(mBitmap);
+    /**
+     * @return the zoom scale of the magnifier.
+     */
+    public float getZoomScale() {
+        return mZoomScale;
     }
 
     private void configureCoordinates(float xPosInView, float yPosInView) {
diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java
index 5dca8e7..0cfc181 100644
--- a/com/android/layoutlib/bridge/Bridge.java
+++ b/com/android/layoutlib/bridge/Bridge.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,659 +14,62 @@
  * limitations under the License.
  */
 
-package com.android.layoutlib.bridge;
-
-import com.android.ide.common.rendering.api.Capability;
-import com.android.ide.common.rendering.api.DrawableParams;
-import com.android.ide.common.rendering.api.Features;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderSession;
+package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession;
 import com.android.ide.common.rendering.api.Result;
 import com.android.ide.common.rendering.api.Result.Status;
 import com.android.ide.common.rendering.api.SessionParams;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.RenderDrawable;
-import com.android.layoutlib.bridge.impl.RenderSessionImpl;
-import com.android.layoutlib.bridge.util.DynamicIdMap;
-import com.android.ninepatch.NinePatchChunk;
-import com.android.resources.ResourceType;
-import com.android.tools.layoutlib.create.MethodAdapter;
-import com.android.tools.layoutlib.create.OverrideMethod;
-import com.android.util.Pair;
 
-import android.annotation.NonNull;
-import android.content.res.BridgeAssetManager;
-import android.graphics.Bitmap;
-import android.graphics.FontFamily_Delegate;
-import android.graphics.Typeface;
-import android.graphics.Typeface_Delegate;
-import android.icu.util.ULocale;
-import android.os.Looper;
-import android.os.Looper_Accessor;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-
-import java.io.File;
-import java.lang.ref.SoftReference;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.util.concurrent.locks.ReentrantLock;
-
-import libcore.io.MemoryMappedFile_Delegate;
-
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
 
 /**
- * Main entry point of the LayoutLib Bridge.
- * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
- * {@link #createSession(SessionParams)}
+ * Legacy Bridge used in the SDK version of layoutlib
  */
 public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
+    private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported";
+    private static final Result NOT_SUPPORTED_RESULT =
+            Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED);
+    private static BufferedImage sImage;
 
-    private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
+    private static class BridgeRenderSession extends RenderSession {
 
-    public static class StaticMethodNotImplementedException extends RuntimeException {
-        private static final long serialVersionUID = 1L;
+        @Override
+        public synchronized BufferedImage getImage() {
+            if (sImage == null) {
+                sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
+                Graphics2D g = sImage.createGraphics();
+                g.clearRect(0, 0, 500, 500);
+                g.drawString(SDK_NOT_SUPPORTED, 20, 20);
+                g.dispose();
+            }
 
-        public StaticMethodNotImplementedException(String msg) {
-            super(msg);
+            return sImage;
+        }
+
+        @Override
+        public Result render(long timeout, boolean forceMeasure) {
+            return NOT_SUPPORTED_RESULT;
+        }
+
+        @Override
+        public Result measure(long timeout) {
+            return NOT_SUPPORTED_RESULT;
+        }
+
+        @Override
+        public Result getResult() {
+            return NOT_SUPPORTED_RESULT;
         }
     }
 
-    /**
-     * Lock to ensure only one rendering/inflating happens at a time.
-     * This is due to some singleton in the Android framework.
-     */
-    private final static ReentrantLock sLock = new ReentrantLock();
 
-    /**
-     * Maps from id to resource type/name. This is for com.android.internal.R
-     */
-    @SuppressWarnings("deprecation")
-    private final static Map<Integer, Pair<ResourceType, String>> sRMap = new HashMap<>();
-
-    /**
-     * Reverse map compared to sRMap, resource type -> (resource name -> id).
-     * This is for com.android.internal.R.
-     */
-    private final static Map<ResourceType, Map<String, Integer>> sRevRMap = new EnumMap<>(ResourceType.class);
-
-    // framework resources are defined as 0x01XX#### where XX is the resource type (layout,
-    // drawable, etc...). Using FF as the type allows for 255 resource types before we get a
-    // collision which should be fine.
-    private final static int DYNAMIC_ID_SEED_START = 0x01ff0000;
-    private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
-
-    private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
-            new WeakHashMap<>();
-    private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
-            new WeakHashMap<>();
-
-    private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>();
-    private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
-            new HashMap<>();
-
-    private static Map<String, Map<String, Integer>> sEnumValueMap;
-    private static Map<String, String> sPlatformProperties;
-
-    /**
-     * A default log than prints to stdout/stderr.
-     */
-    private final static LayoutLog sDefaultLog = new LayoutLog() {
-        @Override
-        public void error(String tag, String message, Object data) {
-            System.err.println(message);
-        }
-
-        @Override
-        public void error(String tag, String message, Throwable throwable, Object data) {
-            System.err.println(message);
-        }
-
-        @Override
-        public void warning(String tag, String message, Object data) {
-            System.out.println(message);
-        }
-    };
-
-    /**
-     * Current log.
-     */
-    private static LayoutLog sCurrentLog = sDefaultLog;
-
-    private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR;
+    @Override
+    public RenderSession createSession(SessionParams params) {
+        return new BridgeRenderSession();
+    }
 
     @Override
     public int getApiLevel() {
-        return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    @Deprecated
-    public EnumSet<Capability> getCapabilities() {
-        // The Capability class is deprecated and frozen. All Capabilities enumerated there are
-        // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf()
-        return EnumSet.allOf(Capability.class);
-    }
-
-    @Override
-    public boolean supports(int feature) {
-        return feature <= LAST_SUPPORTED_FEATURE;
-    }
-
-    @Override
-    public boolean init(Map<String,String> platformProperties,
-            File fontLocation,
-            Map<String, Map<String, Integer>> enumValueMap,
-            LayoutLog log) {
-        sPlatformProperties = platformProperties;
-        sEnumValueMap = enumValueMap;
-
-        BridgeAssetManager.initSystem();
-
-        // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
-        // on static (native) methods which prints the signature on the console and
-        // throws an exception.
-        // This is useful when testing the rendering in ADT to identify static native
-        // methods that are ignored -- layoutlib_create makes them returns 0/false/null
-        // which is generally OK yet might be a problem, so this is how you'd find out.
-        //
-        // Currently layoutlib_create only overrides static native method.
-        // Static non-natives are not overridden and thus do not get here.
-        final String debug = System.getenv("DEBUG_LAYOUT");
-        if (debug != null && !debug.equals("0") && !debug.equals("false")) {
-
-            OverrideMethod.setDefaultListener(new MethodAdapter() {
-                @Override
-                public void onInvokeV(String signature, boolean isNative, Object caller) {
-                    sDefaultLog.error(null, "Missing Stub: " + signature +
-                            (isNative ? " (native)" : ""), null /*data*/);
-
-                    if (debug.equalsIgnoreCase("throw")) {
-                        // Throwing this exception doesn't seem that useful. It breaks
-                        // the layout editor yet doesn't display anything meaningful to the
-                        // user. Having the error in the console is just as useful. We'll
-                        // throw it only if the environment variable is "throw" or "THROW".
-                        throw new StaticMethodNotImplementedException(signature);
-                    }
-                }
-            });
-        }
-
-        // load the fonts.
-        FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath());
-        MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile());
-
-        // now parse com.android.internal.R (and only this one as android.R is a subset of
-        // the internal version), and put the content in the maps.
-        try {
-            Class<?> r = com.android.internal.R.class;
-            // Parse the styleable class first, since it may contribute to attr values.
-            parseStyleable();
-
-            for (Class<?> inner : r.getDeclaredClasses()) {
-                if (inner == com.android.internal.R.styleable.class) {
-                    // Already handled the styleable case. Not skipping attr, as there may be attrs
-                    // that are not referenced from styleables.
-                    continue;
-                }
-                String resTypeName = inner.getSimpleName();
-                ResourceType resType = ResourceType.getEnum(resTypeName);
-                if (resType != null) {
-                    Map<String, Integer> fullMap = null;
-                    switch (resType) {
-                        case ATTR:
-                            fullMap = sRevRMap.get(ResourceType.ATTR);
-                            break;
-                        case STRING:
-                        case STYLE:
-                            // Slightly less than thousand entries in each.
-                            fullMap = new HashMap<>(1280);
-                            // no break.
-                        default:
-                            if (fullMap == null) {
-                                fullMap = new HashMap<>();
-                            }
-                            sRevRMap.put(resType, fullMap);
-                    }
-
-                    for (Field f : inner.getDeclaredFields()) {
-                        // only process static final fields. Since the final attribute may have
-                        // been altered by layoutlib_create, we only check static
-                        if (!isValidRField(f)) {
-                            continue;
-                        }
-                        Class<?> type = f.getType();
-                        if (!type.isArray()) {
-                            Integer value = (Integer) f.get(null);
-                            //noinspection deprecation
-                            sRMap.put(value, Pair.of(resType, f.getName()));
-                            fullMap.put(f.getName(), value);
-                        }
-                    }
-                }
-            }
-        } catch (Exception throwable) {
-            if (log != null) {
-                log.error(LayoutLog.TAG_BROKEN,
-                        "Failed to load com.android.internal.R from the layout library jar",
-                        throwable, null);
-            }
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Tests if the field is pubic, static and one of int or int[].
-     */
-    private static boolean isValidRField(Field field) {
-        int modifiers = field.getModifiers();
-        boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
-        Class<?> type = field.getType();
-        return isAcceptable && type == int.class ||
-                (type.isArray() && type.getComponentType() == int.class);
-
-    }
-
-    private static void parseStyleable() throws Exception {
-        // R.attr doesn't contain all the needed values. There are too many resources in the
-        // framework for all to be in the R class. Only the ones specified manually in
-        // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr
-        // values, we try and find them from the styleables.
-
-        // There were 1500 elements in this map at M timeframe.
-        Map<String, Integer> revRAttrMap = new HashMap<>(2048);
-        sRevRMap.put(ResourceType.ATTR, revRAttrMap);
-        // There were 2000 elements in this map at M timeframe.
-        Map<String, Integer> revRStyleableMap = new HashMap<>(3072);
-        sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap);
-        Class<?> c = com.android.internal.R.styleable.class;
-        Field[] fields = c.getDeclaredFields();
-        // Sort the fields to bring all arrays to the beginning, so that indices into the array are
-        // able to refer back to the arrays (i.e. no forward references).
-        Arrays.sort(fields, (o1, o2) -> {
-            if (o1 == o2) {
-                return 0;
-            }
-            Class<?> t1 = o1.getType();
-            Class<?> t2 = o2.getType();
-            if (t1.isArray() && !t2.isArray()) {
-                return -1;
-            } else if (t2.isArray() && !t1.isArray()) {
-                return 1;
-            }
-            return o1.getName().compareTo(o2.getName());
-        });
-        Map<String, int[]> styleables = new HashMap<>();
-        for (Field field : fields) {
-            if (!isValidRField(field)) {
-                // Only consider public static fields that are int or int[].
-                // Don't check the final flag as it may have been modified by layoutlib_create.
-                continue;
-            }
-            String name = field.getName();
-            if (field.getType().isArray()) {
-                int[] styleableValue = (int[]) field.get(null);
-                styleables.put(name, styleableValue);
-                continue;
-            }
-            // Not an array.
-            String arrayName = name;
-            int[] arrayValue = null;
-            int index;
-            while ((index = arrayName.lastIndexOf('_')) >= 0) {
-                // Find the name of the corresponding styleable.
-                // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity
-                // are mapped to LinearLayout_Layout and not to LinearLayout.
-                arrayName = arrayName.substring(0, index);
-                arrayValue = styleables.get(arrayName);
-                if (arrayValue != null) {
-                    break;
-                }
-            }
-            index = (Integer) field.get(null);
-            if (arrayValue != null) {
-                String attrName = name.substring(arrayName.length() + 1);
-                int attrValue = arrayValue[index];
-                //noinspection deprecation
-                sRMap.put(attrValue, Pair.of(ResourceType.ATTR, attrName));
-                revRAttrMap.put(attrName, attrValue);
-            }
-            //noinspection deprecation
-            sRMap.put(index, Pair.of(ResourceType.STYLEABLE, name));
-            revRStyleableMap.put(name, index);
-        }
-    }
-
-    @Override
-    public boolean dispose() {
-        BridgeAssetManager.clearSystem();
-
-        // dispose of the default typeface.
-        Typeface_Delegate.resetDefaults();
-        Typeface.sDynamicTypefaceCache.evictAll();
-        sProject9PatchCache.clear();
-        sProjectBitmapCache.clear();
-
-        return true;
-    }
-
-    /**
-     * Starts a layout session by inflating and rendering it. The method returns a
-     * {@link RenderSession} on which further actions can be taken.
-     * <p/>
-     * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE},
-     * this method will only inflate the layout but will NOT render it.
-     * @param params the {@link SessionParams} object with all the information necessary to create
-     *           the scene.
-     * @return a new {@link RenderSession} object that contains the result of the layout.
-     * @since 5
-     */
-    @Override
-    public RenderSession createSession(SessionParams params) {
-        try {
-            Result lastResult;
-            RenderSessionImpl scene = new RenderSessionImpl(params);
-            try {
-                prepareThread();
-                lastResult = scene.init(params.getTimeout());
-                if (lastResult.isSuccess()) {
-                    lastResult = scene.inflate();
-
-                    boolean doNotRenderOnCreate = Boolean.TRUE.equals(
-                            params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE));
-                    if (lastResult.isSuccess() && !doNotRenderOnCreate) {
-                        lastResult = scene.render(true /*freshRender*/);
-                    }
-                }
-            } finally {
-                scene.release();
-                cleanupThread();
-            }
-
-            return new BridgeRenderSession(scene, lastResult);
-        } catch (Throwable t) {
-            // get the real cause of the exception.
-            Throwable t2 = t;
-            while (t2.getCause() != null) {
-                t2 = t2.getCause();
-            }
-            return new BridgeRenderSession(null,
-                    ERROR_UNKNOWN.createResult(t2.getMessage(), t));
-        }
-    }
-
-    @Override
-    public Result renderDrawable(DrawableParams params) {
-        try {
-            Result lastResult;
-            RenderDrawable action = new RenderDrawable(params);
-            try {
-                prepareThread();
-                lastResult = action.init(params.getTimeout());
-                if (lastResult.isSuccess()) {
-                    lastResult = action.render();
-                }
-            } finally {
-                action.release();
-                cleanupThread();
-            }
-
-            return lastResult;
-        } catch (Throwable t) {
-            // get the real cause of the exception.
-            Throwable t2 = t;
-            while (t2.getCause() != null) {
-                t2 = t.getCause();
-            }
-            return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
-        }
-    }
-
-    @Override
-    public void clearCaches(Object projectKey) {
-        if (projectKey != null) {
-            sProjectBitmapCache.remove(projectKey);
-            sProject9PatchCache.remove(projectKey);
-        }
-    }
-
-    @Override
-    public Result getViewParent(Object viewObject) {
-        if (viewObject instanceof View) {
-            return Status.SUCCESS.createResult(((View)viewObject).getParent());
-        }
-
-        throw new IllegalArgumentException("viewObject is not a View");
-    }
-
-    @Override
-    public Result getViewIndex(Object viewObject) {
-        if (viewObject instanceof View) {
-            View view = (View) viewObject;
-            ViewParent parentView = view.getParent();
-
-            if (parentView instanceof ViewGroup) {
-                Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
-            }
-
-            return Status.SUCCESS.createResult();
-        }
-
-        throw new IllegalArgumentException("viewObject is not a View");
-    }
-
-    @Override
-    public boolean isRtl(String locale) {
-        return isLocaleRtl(locale);
-    }
-
-    public static boolean isLocaleRtl(String locale) {
-        if (locale == null) {
-            locale = "";
-        }
-        ULocale uLocale = new ULocale(locale);
-        return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL);
-    }
-
-    /**
-     * Returns the lock for the bridge
-     */
-    public static ReentrantLock getLock() {
-        return sLock;
-    }
-
-    /**
-     * Prepares the current thread for rendering.
-     *
-     * Note that while this can be called several time, the first call to {@link #cleanupThread()}
-     * will do the clean-up, and make the thread unable to do further scene actions.
-     */
-    public synchronized static void prepareThread() {
-        // we need to make sure the Looper has been initialized for this thread.
-        // this is required for View that creates Handler objects.
-        if (Looper.myLooper() == null) {
-            Looper.prepareMainLooper();
-        }
-    }
-
-    /**
-     * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
-     * <p>
-     * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
-     * call to this will prevent the thread from doing further scene actions
-     */
-    public synchronized static void cleanupThread() {
-        // clean up the looper
-        Looper_Accessor.cleanupThread();
-    }
-
-    public static LayoutLog getLog() {
-        return sCurrentLog;
-    }
-
-    public static void setLog(LayoutLog log) {
-        // check only the thread currently owning the lock can do this.
-        if (!sLock.isHeldByCurrentThread()) {
-            throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
-        }
-
-        if (log != null) {
-            sCurrentLog = log;
-        } else {
-            sCurrentLog = sDefaultLog;
-        }
-    }
-
-    /**
-     * Returns details of a framework resource from its integer value.
-     * @param value the integer value
-     * @return a Pair containing the resource type and name, or null if the id
-     *     does not match any resource.
-     */
-    @SuppressWarnings("deprecation")
-    public static Pair<ResourceType, String> resolveResourceId(int value) {
-        Pair<ResourceType, String> pair = sRMap.get(value);
-        if (pair == null) {
-            pair = sDynamicIds.resolveId(value);
-        }
-        return pair;
-    }
-
-    /**
-     * Returns the integer id of a framework resource, from a given resource type and resource name.
-     * <p/>
-     * If no resource is found, it creates a dynamic id for the resource.
-     *
-     * @param type the type of the resource
-     * @param name the name of the resource.
-     *
-     * @return an {@link Integer} containing the resource id.
-     */
-    @NonNull
-    public static Integer getResourceId(ResourceType type, String name) {
-        Map<String, Integer> map = sRevRMap.get(type);
-        Integer value = null;
-        if (map != null) {
-            value = map.get(name);
-        }
-
-        return value == null ? sDynamicIds.getId(type, name) : value;
-
-    }
-
-    /**
-     * Returns the list of possible enums for a given attribute name.
-     */
-    public static Map<String, Integer> getEnumValues(String attributeName) {
-        if (sEnumValueMap != null) {
-            return sEnumValueMap.get(attributeName);
-        }
-
-        return null;
-    }
-
-    /**
-     * Returns the platform build properties.
-     */
-    public static Map<String, String> getPlatformProperties() {
-        return sPlatformProperties;
-    }
-
-    /**
-     * Returns the bitmap for a specific path, from a specific project cache, or from the
-     * framework cache.
-     * @param value the path of the bitmap
-     * @param projectKey the key of the project, or null to query the framework cache.
-     * @return the cached Bitmap or null if not found.
-     */
-    public static Bitmap getCachedBitmap(String value, Object projectKey) {
-        if (projectKey != null) {
-            Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
-            if (map != null) {
-                SoftReference<Bitmap> ref = map.get(value);
-                if (ref != null) {
-                    return ref.get();
-                }
-            }
-        } else {
-            SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
-            if (ref != null) {
-                return ref.get();
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Sets a bitmap in a project cache or in the framework cache.
-     * @param value the path of the bitmap
-     * @param bmp the Bitmap object
-     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
-     */
-    public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
-        if (projectKey != null) {
-            Map<String, SoftReference<Bitmap>> map =
-                    sProjectBitmapCache.computeIfAbsent(projectKey, k -> new HashMap<>());
-
-            map.put(value, new SoftReference<>(bmp));
-        } else {
-            sFrameworkBitmapCache.put(value, new SoftReference<>(bmp));
-        }
-    }
-
-    /**
-     * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
-     * framework cache.
-     * @param value the path of the 9 patch
-     * @param projectKey the key of the project, or null to query the framework cache.
-     * @return the cached 9 patch or null if not found.
-     */
-    public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
-        if (projectKey != null) {
-            Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
-
-            if (map != null) {
-                SoftReference<NinePatchChunk> ref = map.get(value);
-                if (ref != null) {
-                    return ref.get();
-                }
-            }
-        } else {
-            SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
-            if (ref != null) {
-                return ref.get();
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Sets a 9 patch chunk in a project cache or in the framework cache.
-     * @param value the path of the 9 patch
-     * @param ninePatch the 9 patch object
-     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
-     */
-    public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
-        if (projectKey != null) {
-            Map<String, SoftReference<NinePatchChunk>> map =
-                    sProject9PatchCache.computeIfAbsent(projectKey, k -> new HashMap<>());
-
-            map.put(value, new SoftReference<>(ninePatch));
-        } else {
-            sFramework9PatchCache.put(value, new SoftReference<>(ninePatch));
-        }
+        return 0;
     }
 }
diff --git a/com/android/server/AlarmManagerService.java b/com/android/server/AlarmManagerService.java
index 4c08f62..3904fc9 100644
--- a/com/android/server/AlarmManagerService.java
+++ b/com/android/server/AlarmManagerService.java
@@ -63,6 +63,7 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
@@ -190,7 +191,8 @@
 
     /**
      * For each uid, this is the last time we dispatched an "allow while idle" alarm,
-     * used to determine the earliest we can dispatch the next such alarm.
+     * used to determine the earliest we can dispatch the next such alarm. Times are in the
+     * 'elapsed' timebase.
      */
     final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
 
@@ -355,6 +357,22 @@
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
             pw.println();
         }
+
+        void dumpProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(ConstantsProto.MIN_FUTURITY_DURATION_MS, MIN_FUTURITY);
+            proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL);
+            proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT);
+            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS,
+                    ALLOW_WHILE_IDLE_SHORT_TIME);
+            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_LONG_DURATION_MS,
+                    ALLOW_WHILE_IDLE_LONG_TIME);
+            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_WHITELIST_DURATION_MS,
+                    ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+
+            proto.end(token);
+        }
     }
 
     final Constants mConstants;
@@ -632,6 +650,20 @@
             b.append('}');
             return b.toString();
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed,
+                long nowRTC) {
+            final long token = proto.start(fieldId);
+
+            proto.write(BatchProto.START_REALTIME, start);
+            proto.write(BatchProto.END_REALTIME, end);
+            proto.write(BatchProto.FLAGS, flags);
+            for (Alarm a : alarms) {
+                a.writeToProto(proto, BatchProto.ALARMS, nowElapsed, nowRTC);
+            }
+
+            proto.end(token);
+        }
     }
 
     static class BatchTimeOrder implements Comparator<Batch> {
@@ -1007,6 +1039,29 @@
                     + ", alarmType=" + mAlarmType
                     + "}";
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(InFlightProto.UID, mUid);
+            proto.write(InFlightProto.TAG, mTag);
+            proto.write(InFlightProto.WHEN_ELAPSED_MS, mWhenElapsed);
+            proto.write(InFlightProto.ALARM_TYPE, mAlarmType);
+            if (mPendingIntent != null) {
+                mPendingIntent.writeToProto(proto, InFlightProto.PENDING_INTENT);
+            }
+            if (mBroadcastStats != null) {
+                mBroadcastStats.writeToProto(proto, InFlightProto.BROADCAST_STATS);
+            }
+            if (mFilterStats != null) {
+                mFilterStats.writeToProto(proto, InFlightProto.FILTER_STATS);
+            }
+            if (mWorkSource != null) {
+                mWorkSource.writeToProto(proto, InFlightProto.WORK_SOURCE);
+            }
+
+            proto.end(token);
+        }
     }
 
     static final class FilterStats {
@@ -1037,6 +1092,20 @@
                     + ", nesting=" + nesting
                     + "}";
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(FilterStatsProto.TAG, mTag);
+            proto.write(FilterStatsProto.LAST_FLIGHT_TIME_REALTIME, lastTime);
+            proto.write(FilterStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime);
+            proto.write(FilterStatsProto.COUNT, count);
+            proto.write(FilterStatsProto.WAKEUP_COUNT, numWakeup);
+            proto.write(FilterStatsProto.START_TIME_REALTIME, startTime);
+            proto.write(FilterStatsProto.NESTING, nesting);
+
+            proto.end(token);
+        }
     }
 
     static final class BroadcastStats {
@@ -1067,6 +1136,20 @@
                     + ", nesting=" + nesting
                     + "}";
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(BroadcastStatsProto.UID, mUid);
+            proto.write(BroadcastStatsProto.PACKAGE_NAME, mPackageName);
+            proto.write(BroadcastStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime);
+            proto.write(BroadcastStatsProto.COUNT, count);
+            proto.write(BroadcastStatsProto.WAKEUP_COUNT, numWakeup);
+            proto.write(BroadcastStatsProto.START_TIME_REALTIME, startTime);
+            proto.write(BroadcastStatsProto.NESTING, nesting);
+
+            proto.end(token);
+        }
     }
 
     final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats
@@ -1128,14 +1211,14 @@
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
         mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
-        
+
         // now that we have initied the driver schedule the alarm
         mClockReceiver = new ClockReceiver();
         mClockReceiver.scheduleTimeTickEvent();
         mClockReceiver.scheduleDateChangedEvent();
         mInteractiveStateReceiver = new InteractiveStateReceiver();
         mUninstallReceiver = new UninstallReceiver();
-        
+
         if (mNativeData != 0) {
             AlarmThread waitThread = new AlarmThread();
             waitThread.start();
@@ -1568,7 +1651,12 @@
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
-            dumpImpl(pw);
+
+            if (args.length > 0 && "--proto".equals(args[0])) {
+                dumpProto(fd);
+            } else {
+                dumpImpl(pw);
+            }
         }
     };
 
@@ -1681,7 +1769,7 @@
                 pw.print("      Idling until: ");
                 if (mPendingIdleUntil != null) {
                     pw.println(mPendingIdleUntil);
-                    mPendingIdleUntil.dump(pw, "        ", nowRTC, nowELAPSED, sdf);
+                    mPendingIdleUntil.dump(pw, "        ", nowELAPSED, nowRTC, sdf);
                 } else {
                     pw.println("null");
                 }
@@ -1691,7 +1779,7 @@
             if (mNextWakeFromIdle != null) {
                 pw.println();
                 pw.print("  Next wake from idle: "); pw.println(mNextWakeFromIdle);
-                mNextWakeFromIdle.dump(pw, "    ", nowRTC, nowELAPSED, sdf);
+                mNextWakeFromIdle.dump(pw, "    ", nowELAPSED, nowRTC, sdf);
             }
 
             pw.println();
@@ -1760,6 +1848,7 @@
                 }
             };
             int len = 0;
+            // Get the top 10 FilterStats, ordered by aggregateTime.
             for (int iu=0; iu<mBroadcastStats.size(); iu++) {
                 ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
                 for (int ip=0; ip<uidStats.size(); ip++) {
@@ -1886,6 +1975,244 @@
         }
     }
 
+    void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        synchronized (mLock) {
+            final long nowRTC = System.currentTimeMillis();
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            proto.write(AlarmManagerServiceProto.CURRENT_TIME, nowRTC);
+            proto.write(AlarmManagerServiceProto.ELAPSED_REALTIME, nowElapsed);
+            proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_CLOCK_TIME,
+                    mLastTimeChangeClockTime);
+            proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_REALTIME,
+                    mLastTimeChangeRealtime);
+
+            mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
+
+            final int foregroundUidsSize = mForegroundUids.size();
+            for (int i = 0; i < foregroundUidsSize; i++) {
+                if (mForegroundUids.valueAt(i)) {
+                    proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
+                }
+            }
+            for (String pkg : mForcedAppStandbyPackages) {
+                proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
+            }
+
+            proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
+            if (!mInteractive) {
+                // Durations
+                proto.write(AlarmManagerServiceProto.TIME_SINCE_NON_INTERACTIVE_MS,
+                        nowElapsed - mNonInteractiveStartTime);
+                proto.write(AlarmManagerServiceProto.MAX_WAKEUP_DELAY_MS,
+                        currentNonWakeupFuzzLocked(nowElapsed));
+                proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_DISPATCH_MS,
+                        nowElapsed - mLastAlarmDeliveryTime);
+                proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_DELIVERY_MS,
+                        nowElapsed - mNextNonWakeupDeliveryTime);
+            }
+
+            proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_ALARM_MS,
+                    mNextNonWakeup - nowElapsed);
+            proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_WAKEUP_MS,
+                    mNextWakeup - nowElapsed);
+            proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_MS,
+                    nowElapsed - mLastWakeup);
+            proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
+                    nowElapsed - mLastWakeupSet);
+            proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
+            for (int i : mDeviceIdleUserWhitelist) {
+                proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
+            }
+
+            final TreeSet<Integer> users = new TreeSet<>();
+            final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
+            for (int i = 0; i < nextAlarmClockForUserSize; i++) {
+                users.add(mNextAlarmClockForUser.keyAt(i));
+            }
+            final int pendingSendNextAlarmClockChangedForUserSize =
+                    mPendingSendNextAlarmClockChangedForUser.size();
+            for (int i = 0; i < pendingSendNextAlarmClockChangedForUserSize; i++) {
+                users.add(mPendingSendNextAlarmClockChangedForUser.keyAt(i));
+            }
+            for (int user : users) {
+                final AlarmManager.AlarmClockInfo next = mNextAlarmClockForUser.get(user);
+                final long time = next != null ? next.getTriggerTime() : 0;
+                final boolean pendingSend = mPendingSendNextAlarmClockChangedForUser.get(user);
+                final long aToken = proto.start(AlarmManagerServiceProto.NEXT_ALARM_CLOCK_METADATA);
+                proto.write(AlarmClockMetadataProto.USER, user);
+                proto.write(AlarmClockMetadataProto.IS_PENDING_SEND, pendingSend);
+                proto.write(AlarmClockMetadataProto.TRIGGER_TIME_MS, time);
+                proto.end(aToken);
+            }
+            for (Batch b : mAlarmBatches) {
+                b.writeToProto(proto, AlarmManagerServiceProto.PENDING_ALARM_BATCHES,
+                        nowElapsed, nowRTC);
+            }
+            for (int i = 0; i < mPendingBackgroundAlarms.size(); i++) {
+                final ArrayList<Alarm> blockedAlarms = mPendingBackgroundAlarms.valueAt(i);
+                if (blockedAlarms != null) {
+                    for (Alarm a : blockedAlarms) {
+                        a.writeToProto(proto,
+                                AlarmManagerServiceProto.PENDING_USER_BLOCKED_BACKGROUND_ALARMS,
+                                nowElapsed, nowRTC);
+                    }
+                }
+            }
+            if (mPendingIdleUntil != null) {
+                mPendingIdleUntil.writeToProto(
+                        proto, AlarmManagerServiceProto.PENDING_IDLE_UNTIL, nowElapsed, nowRTC);
+            }
+            for (Alarm a : mPendingWhileIdleAlarms) {
+                a.writeToProto(proto, AlarmManagerServiceProto.PENDING_WHILE_IDLE_ALARMS,
+                        nowElapsed, nowRTC);
+            }
+            if (mNextWakeFromIdle != null) {
+                mNextWakeFromIdle.writeToProto(proto, AlarmManagerServiceProto.NEXT_WAKE_FROM_IDLE,
+                        nowElapsed, nowRTC);
+            }
+
+            for (Alarm a : mPendingNonWakeupAlarms) {
+                a.writeToProto(proto, AlarmManagerServiceProto.PAST_DUE_NON_WAKEUP_ALARMS,
+                        nowElapsed, nowRTC);
+            }
+
+            proto.write(AlarmManagerServiceProto.DELAYED_ALARM_COUNT, mNumDelayedAlarms);
+            proto.write(AlarmManagerServiceProto.TOTAL_DELAY_TIME_MS, mTotalDelayTime);
+            proto.write(AlarmManagerServiceProto.MAX_DELAY_DURATION_MS, mMaxDelayTime);
+            proto.write(AlarmManagerServiceProto.MAX_NON_INTERACTIVE_DURATION_MS,
+                    mNonInteractiveTime);
+
+            proto.write(AlarmManagerServiceProto.BROADCAST_REF_COUNT, mBroadcastRefCount);
+            proto.write(AlarmManagerServiceProto.PENDING_INTENT_SEND_COUNT, mSendCount);
+            proto.write(AlarmManagerServiceProto.PENDING_INTENT_FINISH_COUNT, mSendFinishCount);
+            proto.write(AlarmManagerServiceProto.LISTENER_SEND_COUNT, mListenerCount);
+            proto.write(AlarmManagerServiceProto.LISTENER_FINISH_COUNT, mListenerFinishCount);
+
+            for (InFlight f : mInFlight) {
+                f.writeToProto(proto, AlarmManagerServiceProto.OUTSTANDING_DELIVERIES);
+            }
+
+            proto.write(AlarmManagerServiceProto.ALLOW_WHILE_IDLE_MIN_DURATION_MS,
+                    mAllowWhileIdleMinTime);
+            for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) {
+                final long token = proto.start(
+                        AlarmManagerServiceProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES);
+                proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.UID,
+                        mLastAllowWhileIdleDispatch.keyAt(i));
+                proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.TIME_MS,
+                        mLastAllowWhileIdleDispatch.valueAt(i));
+                proto.end(token);
+            }
+
+            mLog.writeToProto(proto, AlarmManagerServiceProto.RECENT_PROBLEMS);
+
+            final FilterStats[] topFilters = new FilterStats[10];
+            final Comparator<FilterStats> comparator = new Comparator<FilterStats>() {
+                @Override
+                public int compare(FilterStats lhs, FilterStats rhs) {
+                    if (lhs.aggregateTime < rhs.aggregateTime) {
+                        return 1;
+                    } else if (lhs.aggregateTime > rhs.aggregateTime) {
+                        return -1;
+                    }
+                    return 0;
+                }
+            };
+            int len = 0;
+            // Get the top 10 FilterStats, ordered by aggregateTime.
+            for (int iu = 0; iu < mBroadcastStats.size(); ++iu) {
+                ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+                for (int ip = 0; ip < uidStats.size(); ++ip) {
+                    BroadcastStats bs = uidStats.valueAt(ip);
+                    for (int is = 0; is < bs.filterStats.size(); ++is) {
+                        FilterStats fs = bs.filterStats.valueAt(is);
+                        int pos = len > 0
+                                ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0;
+                        if (pos < 0) {
+                            pos = -pos - 1;
+                        }
+                        if (pos < topFilters.length) {
+                            int copylen = topFilters.length - pos - 1;
+                            if (copylen > 0) {
+                                System.arraycopy(topFilters, pos, topFilters, pos+1, copylen);
+                            }
+                            topFilters[pos] = fs;
+                            if (len < topFilters.length) {
+                                len++;
+                            }
+                        }
+                    }
+                }
+            }
+            for (int i = 0; i < len; ++i) {
+                final long token = proto.start(AlarmManagerServiceProto.TOP_ALARMS);
+                FilterStats fs = topFilters[i];
+
+                proto.write(AlarmManagerServiceProto.TopAlarm.UID, fs.mBroadcastStats.mUid);
+                proto.write(AlarmManagerServiceProto.TopAlarm.PACKAGE_NAME,
+                        fs.mBroadcastStats.mPackageName);
+                fs.writeToProto(proto, AlarmManagerServiceProto.TopAlarm.FILTER);
+
+                proto.end(token);
+            }
+
+            final ArrayList<FilterStats> tmpFilters = new ArrayList<FilterStats>();
+            for (int iu = 0; iu < mBroadcastStats.size(); ++iu) {
+                ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+                for (int ip = 0; ip < uidStats.size(); ++ip) {
+                    final long token = proto.start(AlarmManagerServiceProto.ALARM_STATS);
+
+                    BroadcastStats bs = uidStats.valueAt(ip);
+                    bs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.BROADCAST);
+
+                    // uidStats is an ArrayMap, which we can't sort.
+                    tmpFilters.clear();
+                    for (int is = 0; is < bs.filterStats.size(); ++is) {
+                        tmpFilters.add(bs.filterStats.valueAt(is));
+                    }
+                    Collections.sort(tmpFilters, comparator);
+                    for (FilterStats fs : tmpFilters) {
+                        fs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.FILTERS);
+                    }
+
+                    proto.end(token);
+                }
+            }
+
+            if (RECORD_DEVICE_IDLE_ALARMS) {
+                for (int i = 0; i < mAllowWhileIdleDispatches.size(); i++) {
+                    IdleDispatchEntry ent = mAllowWhileIdleDispatches.get(i);
+                    final long token = proto.start(
+                            AlarmManagerServiceProto.ALLOW_WHILE_IDLE_DISPATCHES);
+
+                    proto.write(IdleDispatchEntryProto.UID, ent.uid);
+                    proto.write(IdleDispatchEntryProto.PKG, ent.pkg);
+                    proto.write(IdleDispatchEntryProto.TAG, ent.tag);
+                    proto.write(IdleDispatchEntryProto.OP, ent.op);
+                    proto.write(IdleDispatchEntryProto.ENTRY_CREATION_REALTIME,
+                            ent.elapsedRealtime);
+                    proto.write(IdleDispatchEntryProto.ARG_REALTIME, ent.argRealtime);
+
+                    proto.end(token);
+                }
+            }
+
+            if (WAKEUP_STATS) {
+                for (WakeupEvent event : mRecentWakeups) {
+                    final long token = proto.start(AlarmManagerServiceProto.RECENT_WAKEUP_HISTORY);
+                    proto.write(WakeupEventProto.UID, event.uid);
+                    proto.write(WakeupEventProto.ACTION, event.action);
+                    proto.write(WakeupEventProto.WHEN, event.when);
+                    proto.end(token);
+                }
+            }
+        }
+
+        proto.flush();
+    }
+
     private void logBatchesLocked(SimpleDateFormat sdf) {
         ByteArrayOutputStream bs = new ByteArrayOutputStream(2048);
         PrintWriter pw = new PrintWriter(bs);
@@ -2328,24 +2655,24 @@
                 alarmSeconds = when / 1000;
                 alarmNanoseconds = (when % 1000) * 1000 * 1000;
             }
-            
+
             set(mNativeData, type, alarmSeconds, alarmNanoseconds);
         } else {
             Message msg = Message.obtain();
             msg.what = ALARM_EVENT;
-            
+
             mHandler.removeMessages(ALARM_EVENT);
             mHandler.sendMessageAtTime(msg, when);
         }
     }
 
     private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
-            String prefix, String label, long nowRTC, long nowELAPSED, SimpleDateFormat sdf) {
+            String prefix, String label, long nowELAPSED, long nowRTC, SimpleDateFormat sdf) {
         for (int i=list.size()-1; i>=0; i--) {
             Alarm a = list.get(i);
             pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
                     pw.print(": "); pw.println(a);
-            a.dump(pw, prefix + "  ", nowRTC, nowELAPSED, sdf);
+            a.dump(pw, prefix + "  ", nowELAPSED, nowRTC, sdf);
         }
     }
 
@@ -2355,8 +2682,6 @@
         case RTC_WAKEUP : return "RTC_WAKEUP";
         case ELAPSED_REALTIME : return "ELAPSED";
         case ELAPSED_REALTIME_WAKEUP: return "ELAPSED_WAKEUP";
-        default:
-            break;
         }
         return "--unknown--";
     }
@@ -2368,7 +2693,7 @@
             final String label = labelForType(a.type);
             pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
                     pw.print(": "); pw.println(a);
-            a.dump(pw, prefix + "  ", nowRTC, nowELAPSED, sdf);
+            a.dump(pw, prefix + "  ", nowELAPSED, nowRTC, sdf);
         }
     }
 
@@ -2533,7 +2858,7 @@
             return 0;
         }
     }
-    
+
     private static class Alarm {
         public final int type;
         public final long origWhen;
@@ -2627,7 +2952,7 @@
             return sb.toString();
         }
 
-        public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED,
+        public void dump(PrintWriter pw, String prefix, long nowELAPSED, long nowRTC,
                 SimpleDateFormat sdf) {
             final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
             pw.print(prefix); pw.print("tag="); pw.println(statsTag);
@@ -2656,6 +2981,30 @@
                 pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder());
             }
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed,
+                long nowRTC) {
+            final long token = proto.start(fieldId);
+
+            proto.write(AlarmProto.TAG, statsTag);
+            proto.write(AlarmProto.TYPE, type);
+            proto.write(AlarmProto.WHEN_ELAPSED_MS, whenElapsed - nowElapsed);
+            proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength);
+            proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval);
+            proto.write(AlarmProto.COUNT, count);
+            proto.write(AlarmProto.FLAGS, flags);
+            if (alarmClock != null) {
+                alarmClock.writeToProto(proto, AlarmProto.ALARM_CLOCK);
+            }
+            if (operation != null) {
+                operation.writeToProto(proto, AlarmProto.OPERATION);
+            }
+            if (listener != null) {
+                proto.write(AlarmProto.LISTENER, listener.asBinder().toString());
+            }
+
+            proto.end(token);
+        }
     }
 
     void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) {
@@ -2752,7 +3101,7 @@
         {
             super("AlarmManager");
         }
-        
+
         public void run()
         {
             ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
@@ -2918,10 +3267,10 @@
         public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2;
         public static final int LISTENER_TIMEOUT = 3;
         public static final int REPORT_ALARMS_ACTIVE = 4;
-        
+
         public AlarmHandler() {
         }
-        
+
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case ALARM_EVENT: {
@@ -2969,7 +3318,7 @@
             }
         }
     }
-    
+
     class ClockReceiver extends BroadcastReceiver {
         public ClockReceiver() {
             IntentFilter filter = new IntentFilter();
@@ -2977,7 +3326,7 @@
             filter.addAction(Intent.ACTION_DATE_CHANGED);
             getContext().registerReceiver(this, filter);
         }
-        
+
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
@@ -2996,7 +3345,7 @@
                 scheduleDateChangedEvent();
             }
         }
-        
+
         public void scheduleTimeTickEvent() {
             final long currentTime = System.currentTimeMillis();
             final long nextTime = 60000 * ((currentTime / 60000) + 1);
@@ -3026,7 +3375,7 @@
                     Process.myUid(), "android");
         }
     }
-    
+
     class InteractiveStateReceiver extends BroadcastReceiver {
         public InteractiveStateReceiver() {
             IntentFilter filter = new IntentFilter();
@@ -3059,7 +3408,7 @@
             sdFilter.addAction(Intent.ACTION_UID_REMOVED);
             getContext().registerReceiver(this, sdFilter);
         }
-        
+
         @Override
         public void onReceive(Context context, Intent intent) {
             synchronized (mLock) {
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index 46b671b..68546bd 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -43,6 +43,7 @@
 import android.hardware.health.V2_0.IHealth;
 import android.hardware.health.V2_0.Result;
 import android.os.BatteryManager;
+import android.os.BatteryManagerProto;
 import android.os.BatteryManagerInternal;
 import android.os.BatteryProperty;
 import android.os.Binder;
@@ -252,39 +253,33 @@
             mHealthServiceWrapper.init(mHealthHalCallback,
                     new HealthServiceWrapper.IServiceManagerSupplier() {},
                     new HealthServiceWrapper.IHealthSupplier() {});
-        } catch (RemoteException | NoSuchElementException ex) {
-            Slog.w(TAG, "health: cannot register callback. "
-                        + "BatteryService will be started with dummy values. Reason: "
-                        + ex.getClass().getSimpleName() + ": " + ex.getMessage());
-            update(new HealthInfo());
-            return;
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "health: cannot register callback. (RemoteException)");
+            throw ex.rethrowFromSystemServer();
+        } catch (NoSuchElementException ex) {
+            Slog.e(TAG, "health: cannot register callback. (no supported health HAL service)");
+            throw ex;
         }
 
         // init register for new service notifications, and IServiceManager should return the
         // existing service in a near future. Wait for this.update() to instantiate
         // the initial mHealthInfo.
-        long timeWaited = 0;
+        long beforeWait = SystemClock.uptimeMillis();
         synchronized (mLock) {
-            long beforeWait = SystemClock.uptimeMillis();
-            while (mHealthInfo == null &&
-                    (timeWaited = SystemClock.uptimeMillis() - beforeWait) < HEALTH_HAL_WAIT_MS) {
+            while (mHealthInfo == null) {
+                Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait) +
+                        "ms for callbacks. Waiting another " + HEALTH_HAL_WAIT_MS + " ms...");
                 try {
-                    mLock.wait(HEALTH_HAL_WAIT_MS - timeWaited);
+                    mLock.wait(HEALTH_HAL_WAIT_MS);
                 } catch (InterruptedException ex) {
-                    break;
+                    Slog.i(TAG, "health: InterruptedException when waiting for update. "
+                        + " Continuing...");
                 }
             }
-            if (mHealthInfo == null) {
-                Slog.w(TAG, "health: Waited " + timeWaited + "ms for callbacks but received "
-                        + "nothing. BatteryService will be started with dummy values.");
-                update(new HealthInfo());
-                return;
-            }
         }
 
-        if (DEBUG) {
-            Slog.d(TAG, "health: Waited " + timeWaited + "ms and received the update.");
-        }
+        Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait)
+                + "ms and received the update.");
     }
 
     private void updateBatteryWarningLevelLocked() {
@@ -913,13 +908,13 @@
 
         synchronized (mLock) {
             proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
-            int batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_NONE;
+            int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE;
             if (mHealthInfo.legacy.chargerAcOnline) {
-                batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_AC;
+                batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC;
             } else if (mHealthInfo.legacy.chargerUsbOnline) {
-                batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_USB;
+                batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB;
             } else if (mHealthInfo.legacy.chargerWirelessOnline) {
-                batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_WIRELESS;
+                batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS;
             }
             proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
             proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
@@ -1060,6 +1055,7 @@
         }
         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:
@@ -1101,8 +1097,10 @@
             }
             return outResult.value;
         }
-        public void scheduleUpdate() {
-            Slog.e(TAG, "health: must not call scheduleUpdate on battery properties");
+        public void scheduleUpdate() throws RemoteException {
+            IHealth service = mHealthServiceWrapper.getLastService();
+            if (service == null) throw new RemoteException("no health service");
+            service.update();
         }
     }
 
diff --git a/com/android/server/ConnectivityService.java b/com/android/server/ConnectivityService.java
index 348c799..7e65d36 100644
--- a/com/android/server/ConnectivityService.java
+++ b/com/android/server/ConnectivityService.java
@@ -70,7 +70,6 @@
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.Uri;
-import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.util.MultinetworkPolicyTracker;
@@ -127,6 +126,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
@@ -143,6 +143,7 @@
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.net.LockdownVpnTracker;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.utils.PriorityDump;
 
 import com.google.android.collect.Lists;
 
@@ -682,6 +683,28 @@
     }
     private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
 
+    /**
+     * Helper class which parses out priority arguments and dumps sections according to their
+     * priority. If priority arguments are omitted, function calls the legacy dump command.
+     */
+    private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+        @Override
+        public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            doDump(fd, pw, new String[] {DIAG_ARG}, asProto);
+            doDump(fd, pw, new String[] {SHORT_ARG}, asProto);
+        }
+
+        @Override
+        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            doDump(fd, pw, args, asProto);
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+           doDump(fd, pw, args, asProto);
+        }
+    };
+
     public ConnectivityService(Context context, INetworkManagementService netManager,
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
         this(context, netManager, statsService, policyManager, new IpConnectivityLog());
@@ -1862,8 +1885,13 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        PriorityDump.dump(mPriorityDumper, fd, writer, args);
+    }
+
+    private void doDump(FileDescriptor fd, PrintWriter writer, String[] args, boolean asProto) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        if (asProto) return;
 
         if (argsContain(args, DIAG_ARG)) {
             dumpNetworkDiagnostics(pw);
@@ -2265,7 +2293,7 @@
                 // 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.
-                logDefaultNetworkEvent(null, nai);
+                metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai);
             }
             notifyIfacesChangedForNetworkStats();
             // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -4995,7 +5023,8 @@
             // Notify system services that this network is up.
             makeDefault(newNetwork);
             // Log 0 -> X and Y -> X default network transitions, where X is the new default.
-            logDefaultNetworkEvent(newNetwork, oldDefaultNetwork);
+            metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
+                    newNetwork, oldDefaultNetwork);
             // Have a new default network, release the transition wakelock in
             scheduleReleaseNetworkTransitionWakelock();
         }
@@ -5554,25 +5583,10 @@
         return ServiceManager.checkService(name) != null;
     }
 
-    private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
-        int newNetid = NETID_UNSET;
-        int prevNetid = NETID_UNSET;
-        int[] transports = new int[0];
-        boolean hadIPv4 = false;
-        boolean hadIPv6 = false;
-
-        if (newNai != null) {
-            newNetid = newNai.network.netId;
-            transports = newNai.networkCapabilities.getTransportTypes();
-        }
-        if (prevNai != null) {
-            prevNetid = prevNai.network.netId;
-            final LinkProperties lp = prevNai.linkProperties;
-            hadIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
-            hadIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
-        }
-
-        mMetricsLog.log(new DefaultNetworkEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6));
+    @VisibleForTesting
+    protected IpConnectivityMetrics.Logger metricsLogger() {
+        return checkNotNull(LocalServices.getService(IpConnectivityMetrics.Logger.class),
+                "no IpConnectivityMetrics service");
     }
 
     private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
diff --git a/com/android/server/InputMethodManagerService.java b/com/android/server/InputMethodManagerService.java
index 9da3757..f007bcc 100644
--- a/com/android/server/InputMethodManagerService.java
+++ b/com/android/server/InputMethodManagerService.java
@@ -1470,7 +1470,9 @@
                 broadcastFilter.addAction(ACTION_SHOW_INPUT_METHOD_PICKER);
                 mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
 
-                buildInputMethodListLocked(true /* resetDefaultEnabledIme */);
+                final String defaultImiId = mSettings.getSelectedInputMethod();
+                final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
+                buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
                 resetDefaultImeLocked(mContext);
                 updateFromSettingsLocked(true);
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
diff --git a/com/android/server/IpSecService.java b/com/android/server/IpSecService.java
index cf1d33c..1154fbe 100644
--- a/com/android/server/IpSecService.java
+++ b/com/android/server/IpSecService.java
@@ -754,7 +754,7 @@
      * and re-binding, during which the system could *technically* hand that port out to someone
      * else.
      */
-    private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
+    private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
         for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
             try {
                 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
@@ -763,7 +763,7 @@
                 Os.close(probeSocket);
                 Log.v(TAG, "Binding to port " + port);
                 Os.bind(sockFd, INADDR_ANY, port);
-                return;
+                return port;
             } catch (ErrnoException e) {
                 // Someone miraculously claimed the port just after we closed probeSocket.
                 if (e.errno == OsConstants.EADDRINUSE) {
@@ -803,7 +803,7 @@
                 Log.v(TAG, "Binding to port " + port);
                 Os.bind(sockFd, INADDR_ANY, port);
             } else {
-                bindToRandomPort(sockFd);
+                port = bindToRandomPort(sockFd);
             }
             // This code is common to both the unspecified and specified port cases
             Os.setsockoptInt(
@@ -944,13 +944,13 @@
                                 (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
                                 spi,
                                 (auth != null) ? auth.getName() : "",
-                                (auth != null) ? auth.getKey() : null,
+                                (auth != null) ? auth.getKey() : new byte[] {},
                                 (auth != null) ? auth.getTruncationLengthBits() : 0,
                                 (crypt != null) ? crypt.getName() : "",
-                                (crypt != null) ? crypt.getKey() : null,
+                                (crypt != null) ? crypt.getKey() : new byte[] {},
                                 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
                                 (authCrypt != null) ? authCrypt.getName() : "",
-                                (authCrypt != null) ? authCrypt.getKey() : null,
+                                (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
                                 (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
                                 encapType,
                                 encapLocalPort,
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index cd25610..bfbce40 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -641,8 +641,8 @@
                     break;
                 }
                 case H_PARTITION_FORGET: {
-                    final String partGuid = (String) msg.obj;
-                    forgetPartition(partGuid);
+                    final VolumeRecord rec = (VolumeRecord) msg.obj;
+                    forgetPartition(rec.partGuid, rec.fsUuid);
                     break;
                 }
                 case H_RESET: {
@@ -1694,7 +1694,7 @@
         synchronized (mLock) {
             final VolumeRecord rec = mRecords.remove(fsUuid);
             if (rec != null && !TextUtils.isEmpty(rec.partGuid)) {
-                mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
+                mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
             }
             mCallbacks.notifyVolumeForgotten(fsUuid);
 
@@ -1718,7 +1718,7 @@
                 final String fsUuid = mRecords.keyAt(i);
                 final VolumeRecord rec = mRecords.valueAt(i);
                 if (!TextUtils.isEmpty(rec.partGuid)) {
-                    mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
+                    mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
                 }
                 mCallbacks.notifyVolumeForgotten(fsUuid);
             }
@@ -1733,9 +1733,9 @@
         }
     }
 
-    private void forgetPartition(String partGuid) {
+    private void forgetPartition(String partGuid, String fsUuid) {
         try {
-            mVold.forgetPartition(partGuid);
+            mVold.forgetPartition(partGuid, fsUuid);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index d9db22e..de5d879 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -125,7 +125,10 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 
-import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PROTO;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 public final class SystemServer {
@@ -827,7 +830,7 @@
                     mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                     !mFirstBoot, mOnlyCore, new PhoneWindowManager());
             ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
-                    DUMP_PRIORITY_CRITICAL);
+                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
             ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
             traceEnd();
 
@@ -1138,7 +1141,9 @@
                 try {
                     connectivity = new ConnectivityService(
                             context, networkManagement, networkStats, networkPolicy);
-                    ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
+                    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) {
@@ -1551,6 +1556,11 @@
             traceEnd();
         }
 
+        // Statsd helper
+        traceBeginAndSlog("StartStatsCompanionService");
+        mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
+        traceEnd();
+
         // Before things start rolling, be sure we have decided whether
         // we are in safe mode.
         final boolean safeMode = wm.detectSafeMode();
diff --git a/com/android/server/accounts/AccountManagerService.java b/com/android/server/accounts/AccountManagerService.java
index c684032..0d4f5cb 100644
--- a/com/android/server/accounts/AccountManagerService.java
+++ b/com/android/server/accounts/AccountManagerService.java
@@ -5101,8 +5101,16 @@
                 logStatement.bindLong(4, callingUid);
                 logStatement.bindString(5, tableName);
                 logStatement.bindLong(6, userDebugDbInsertionPoint);
-                logStatement.execute();
-                logStatement.clearBindings();
+                try {
+                    logStatement.execute();
+                } catch (IllegalStateException e) {
+                    // Guard against crash, DB can already be closed
+                    // since this statement is executed on a handler thread
+                    Slog.w(TAG, "Failed to insert a log record. accountId=" + accountId
+                            + " action=" + action + " tableName=" + tableName + " Error: " + e);
+                } finally {
+                    logStatement.clearBindings();
+                }
             }
         }
 
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
index 089db87..c04ddf8 100644
--- a/com/android/server/am/ActivityDisplay.java
+++ b/com/android/server/am/ActivityDisplay.java
@@ -38,7 +38,9 @@
 import static com.android.server.am.proto.ActivityDisplayProto.STACKS;
 import static com.android.server.am.proto.ActivityDisplayProto.ID;
 
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.WindowConfiguration;
 import android.util.IntArray;
 import android.util.Slog;
@@ -206,6 +208,18 @@
     }
 
     /**
+     * Returns an existing stack compatible with the input params or creates one
+     * if a compatible stack doesn't exist.
+     * @see #getOrCreateStack(int, int, boolean)
+     */
+    <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, int activityType,
+            boolean onTop) {
+        final int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
+        return getOrCreateStack(windowingMode, activityType, onTop);
+    }
+
+    /**
      * Creates a stack matching the input windowing mode and activity type on this display.
      * @param windowingMode The windowing mode the stack should be created in. If
      *                      {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
@@ -235,7 +249,7 @@
         }
 
         final ActivityManagerService service = mSupervisor.mService;
-        if (!mSupervisor.isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
+        if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
                 service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
                 service.mSupportsPictureInPicture, activityType)) {
             throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
@@ -252,33 +266,27 @@
             }
         }
 
-        windowingMode = updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
-
         final int stackId = mSupervisor.getNextStackId();
-
-        final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop);
-
-        if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            // Make sure recents stack exist when creating a dock stack as it normally need to be on
-            // the other side of the docked stack and we make visibility decisions based on that.
-            // TODO: Not sure if this is needed after we change to calculate visibility based on
-            // stack z-order vs. id.
-            getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
-        }
-
-        return stack;
+        return createStackUnchecked(windowingMode, activityType, stackId, onTop);
     }
 
     @VisibleForTesting
     <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
             int stackId, boolean onTop) {
-        switch (windowingMode) {
-            case WINDOWING_MODE_PINNED:
-                return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
-            default:
-                return (T) new ActivityStack(
-                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
+        if (windowingMode == WINDOWING_MODE_PINNED) {
+            return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
         }
+        final T stack = (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;
     }
 
     /**
@@ -372,6 +380,105 @@
         }
     }
 
+    /**
+     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+     * @param windowingMode The windowing mode we are checking support for.
+     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+     * @param supportsFreeform If we should consider support for freeform multi-window.
+     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+     * @param activityType The activity type under consideration.
+     * @return true if the windowing mode is supported.
+     */
+    private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+            int activityType) {
+
+        if (windowingMode == WINDOWING_MODE_UNDEFINED
+                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            return true;
+        }
+        if (!supportsMultiWindow) {
+            return false;
+        }
+
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
+                    windowingMode, activityType);
+        }
+
+        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+            return false;
+        }
+
+        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+            return false;
+        }
+        return true;
+    }
+
+    int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable TaskRecord task, int activityType) {
+
+        // First preference if the windowing mode in the activity options if set.
+        int windowingMode = (options != null)
+                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+        // If windowing mode is unset, then next preference is the candidate task, then the
+        // activity record.
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            if (task != null) {
+                windowingMode = task.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+                windowingMode = r.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+                // Use the display's windowing mode.
+                windowingMode = getWindowingMode();
+            }
+        }
+
+        // Make sure the windowing mode we are trying to use makes sense for what is supported.
+        final ActivityManagerService service = mSupervisor.mService;
+        boolean supportsMultiWindow = service.mSupportsMultiWindow;
+        boolean supportsSplitScreen = service.mSupportsSplitScreenMultiWindow;
+        boolean supportsFreeform = service.mSupportsFreeformWindowManagement;
+        boolean supportsPip = service.mSupportsPictureInPicture;
+        if (supportsMultiWindow) {
+            if (task != null) {
+                supportsMultiWindow = task.isResizeable();
+                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+                // TODO: Do we need to check for freeform and Pip support here?
+            } else if (r != null) {
+                supportsMultiWindow = r.isResizeable();
+                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+                supportsFreeform = r.supportsFreeform();
+                supportsPip = r.supportsPictureInPicture();
+            }
+        }
+
+        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+        if (!inSplitScreenMode
+                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+            // trying to launch in split-screen secondary.
+            windowingMode = WINDOWING_MODE_FULLSCREEN;
+        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+                && supportsSplitScreen) {
+            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+        }
+
+        if (windowingMode != WINDOWING_MODE_UNDEFINED
+                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+                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) {
@@ -408,6 +515,14 @@
         }
     }
 
+    /** We are in the process of exiting split-screen mode. */
+    void onExitingSplitScreenMode() {
+        // Remove reference to the primary-split-screen stack so it no longer has any effect on the
+        // display. For example, we want to be able to create fullscreen stack for standard activity
+        // types when exiting split-screen mode.
+        mSplitScreenPrimaryStack = null;
+    }
+
     ActivityStack getSplitScreenPrimaryStack() {
         return mSplitScreenPrimaryStack;
     }
@@ -424,21 +539,6 @@
         return mPinnedStack != null;
     }
 
-    int updateWindowingModeForSplitScreenIfNeeded(int windowingMode, int activityType) {
-        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
-        if (!inSplitScreenMode
-                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
-            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
-            // trying to launch in split-screen secondary.
-            return WINDOWING_MODE_FULLSCREEN;
-        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
-                && WindowConfiguration.supportSplitScreenWindowingMode(
-                windowingMode, activityType)) {
-            return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-        }
-        return windowingMode;
-    }
-
     @Override
     public String toString() {
         return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
@@ -504,7 +604,7 @@
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         proto.write(ID, mDisplayId);
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index e1e53b3..f2e0493 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -23,16 +23,20 @@
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 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.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.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 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;
@@ -49,9 +53,10 @@
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
 import static android.os.Build.VERSION_CODES.N;
-import static android.os.IServiceManager.DUMP_PRIORITY_CRITICAL;
-import static android.os.IServiceManager.DUMP_PRIORITY_HIGH;
-import static android.os.IServiceManager.DUMP_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PROTO;
 import static android.os.Process.BLUETOOTH_UID;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.FIRST_ISOLATED_UID;
@@ -226,6 +231,8 @@
 import android.app.ProfilerInfo;
 import android.app.RemoteAction;
 import android.app.WaitResult;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
 import android.app.admin.DevicePolicyManager;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
@@ -333,6 +340,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.StatsLog;
 import android.util.TimingsTraceLog;
 import android.util.DebugUtils;
 import android.util.DisplayMetrics;
@@ -368,6 +376,7 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.BinderInternal;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TransferPipe;
@@ -665,7 +674,7 @@
     /**
      * List of intents that were used to start the most recent tasks.
      */
-    final RecentTasks mRecentTasks;
+    private final RecentTasks mRecentTasks;
 
     /**
      * For addAppTask: cached of the last activity component that was added.
@@ -714,30 +723,36 @@
      */
     private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
         @Override
-        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, new String[] {"activities"});
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                boolean asProto) {
+            if (asProto) return;
+            doDump(fd, pw, new String[]{"activities"}, asProto);
         }
 
         @Override
-        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, new String[] {"settings"});
-            doDump(fd, pw, new String[] {"intents"});
-            doDump(fd, pw, new String[] {"broadcasts"});
-            doDump(fd, pw, new String[] {"providers"});
-            doDump(fd, pw, new String[] {"permissions"});
-            doDump(fd, pw, new String[] {"services"});
-            doDump(fd, pw, new String[] {"recents"});
-            doDump(fd, pw, new String[] {"lastanr"});
-            doDump(fd, pw, new String[] {"starter"});
-            if (mAssociations.size() > 0) {
-                doDump(fd, pw, new String[] {"associations"});
+        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            if (asProto) {
+                doDump(fd, pw, new String[0], asProto);
+            } else {
+                doDump(fd, pw, new String[]{"settings"}, asProto);
+                doDump(fd, pw, new String[]{"intents"}, asProto);
+                doDump(fd, pw, new String[]{"broadcasts"}, asProto);
+                doDump(fd, pw, new String[]{"providers"}, asProto);
+                doDump(fd, pw, new String[]{"permissions"}, asProto);
+                doDump(fd, pw, new String[]{"services"}, asProto);
+                doDump(fd, pw, new String[]{"recents"}, asProto);
+                doDump(fd, pw, new String[]{"lastanr"}, asProto);
+                doDump(fd, pw, new String[]{"starter"}, asProto);
+                if (mAssociations.size() > 0) {
+                    doDump(fd, pw, new String[]{"associations"}, asProto);
+                }
+                doDump(fd, pw, new String[]{"processes"}, asProto);
             }
-            doDump(fd, pw, new String[] {"processes"});
         }
 
         @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, args);
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            doDump(fd, pw, args, asProto);
         }
     };
 
@@ -2030,7 +2045,9 @@
                 synchronized (ActivityManagerService.this) {
                     for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
                         ProcessRecord r = mLruProcesses.get(i);
-                        if (r.thread != null) {
+                        // Don't dispatch to isolated processes as they can't access
+                        // ConnectivityManager and don't have network privileges anyway.
+                        if (r.thread != null && !r.isolated) {
                             try {
                                 r.thread.setHttpProxy(host, port, exclList, pacFileUrl);
                             } catch (RemoteException ex) {
@@ -2483,15 +2500,15 @@
     public void setSystemProcess() {
         try {
             ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
-                    DUMP_PRIORITY_CRITICAL | DUMP_PRIORITY_NORMAL);
+                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
             ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
             ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
-                    DUMP_PRIORITY_HIGH | DUMP_PRIORITY_NORMAL);
+                    DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
             ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
             ServiceManager.addService("dbinfo", new DbBinder(this));
             if (MONITOR_CPU_USAGE) {
                 ServiceManager.addService("cpuinfo", new CpuBinder(this),
-                        /* allowIsolated= */ false, DUMP_PRIORITY_CRITICAL);
+                        /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
             }
             ServiceManager.addService("permission", new PermissionController(this));
             ServiceManager.addService("processinfo", new ProcessInfoService(this));
@@ -2544,7 +2561,9 @@
         private final PriorityDump.PriorityDumper mPriorityDumper =
                 new PriorityDump.PriorityDumper() {
             @Override
-            public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+            public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args,
+                    boolean asProto) {
+                if (asProto) return;
                 mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false, null);
             }
         };
@@ -2594,7 +2613,9 @@
         private final PriorityDump.PriorityDumper mPriorityDumper =
                 new PriorityDump.PriorityDumper() {
             @Override
-            public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+            public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                    boolean asProto) {
+                if (asProto) return;
                 if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                         "cpuinfo", pw)) return;
                 synchronized (mActivityManagerService.mProcessCpuTracker) {
@@ -2764,7 +2785,7 @@
         mTaskChangeNotificationController =
                 new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
         mActivityStarter = new ActivityStarter(this);
-        mRecentTasks = new RecentTasks(this, mStackSupervisor);
+        mRecentTasks = createRecentTasks();
         mStackSupervisor.setRecentTasks(mRecentTasks);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
 
@@ -2810,6 +2831,14 @@
         return new ActivityStackSupervisor(this, mHandler.getLooper());
     }
 
+    protected RecentTasks createRecentTasks() {
+        return new RecentTasks(this, mStackSupervisor);
+    }
+
+    RecentTasks getRecentTasks() {
+        return mRecentTasks;
+    }
+
     public void setSystemServiceManager(SystemServiceManager mgr) {
         mSystemServiceManager = mgr;
     }
@@ -3145,6 +3174,7 @@
             synchronized (this) {
                 final ActivityStack stack = mStackSupervisor.getStack(stackId);
                 if (stack == null) {
+                    Slog.w(TAG, "setFocusedStack: No stack with id=" + stackId);
                     return;
                 }
                 final ActivityRecord r = stack.topRunningActivityLocked();
@@ -3181,7 +3211,8 @@
     /** Sets the task stack listener that gets callbacks when a task stack changes. */
     @Override
     public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "registerTaskStackListener()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "registerTaskStackListener()");
         mTaskChangeNotificationController.registerTaskStackListener(listener);
     }
 
@@ -3190,7 +3221,8 @@
      */
     @Override
     public void unregisterTaskStackListener(ITaskStackListener listener) throws RemoteException {
-         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "unregisterTaskStackListener()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "unregisterTaskStackListener()");
          mTaskChangeNotificationController.unregisterTaskStackListener(listener);
      }
 
@@ -4868,12 +4900,9 @@
 
     @Override
     public final int startActivityFromRecents(int taskId, Bundle bOptions) {
-        if (checkCallingPermission(START_TASKS_FROM_RECENTS) != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: startActivityFromRecents called without " +
-                    START_TASKS_FROM_RECENTS;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
+        enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
+                "startActivityFromRecents()");
+
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -8081,7 +8110,7 @@
                     // Adjust the source bounds by the insets for the transition down
                     final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint());
                     mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio,
-                            true /* moveHomeStackToFront */, "enterPictureInPictureMode");
+                            "enterPictureInPictureMode");
                     final PinnedActivityStack stack = r.getStack();
                     stack.setPictureInPictureAspectRatio(aspectRatio);
                     stack.setPictureInPictureActions(actions);
@@ -8325,9 +8354,6 @@
         }
     }
 
-    /**
-     * This can be called with or without the global lock held.
-     */
     int checkComponentPermission(String permission, int pid, int uid,
             int owningUid, boolean exported) {
         if (pid == MY_PID) {
@@ -8402,6 +8428,15 @@
     }
 
     /**
+     * This can be called with or without the global lock held.
+     */
+    void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
+        if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
+            enforceCallingPermission(permission, func);
+        }
+    }
+
+    /**
      * Determine if UID is holding permissions required to access {@link Uri} in
      * the given {@link ProviderInfo}. Final permission checking is always done
      * in {@link ContentProvider}.
@@ -9767,25 +9802,34 @@
     }
 
     @Override
-    public List<RunningTaskInfo> getTasks(int maxNum, int flags) {
+    public List<RunningTaskInfo> getTasks(int maxNum) {
+       return getFilteredTasks(maxNum, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED);
+    }
+
+    @Override
+    public List<RunningTaskInfo> getFilteredTasks(int maxNum, @ActivityType int ignoreActivityType,
+            @WindowingMode int ignoreWindowingMode) {
         final int callingUid = Binder.getCallingUid();
-        ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();
+        ArrayList<RunningTaskInfo> list = new ArrayList<>();
 
         synchronized(this) {
-            if (DEBUG_ALL) Slog.v(
-                TAG, "getTasks: max=" + maxNum + ", flags=" + flags);
+            if (DEBUG_ALL) Slog.v(TAG, "getTasks: max=" + maxNum);
 
             final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(),
                     callingUid);
-
-            // TODO: Improve with MRU list from all ActivityStacks.
-            mStackSupervisor.getTasksLocked(maxNum, list, callingUid, allowed);
+            mStackSupervisor.getRunningTasks(maxNum, list, ignoreActivityType,
+                    ignoreWindowingMode, callingUid, allowed);
         }
 
         return list;
     }
 
     private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) {
+        if (mRecentTasks.isCallerRecents(callingUid)) {
+            // Always allow the recents component to get tasks
+            return true;
+        }
+
         boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS,
                 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
         if (!allowed) {
@@ -9833,8 +9877,7 @@
     @Override
     public ActivityManager.TaskDescription getTaskDescription(int id) {
         synchronized (this) {
-            enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
-                    "getTaskDescription()");
+            enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getTaskDescription()");
             final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id,
                     MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
             if (tr != null) {
@@ -10028,7 +10071,8 @@
 
     @Override
     public void cancelTaskWindowTransition(int taskId) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskWindowTransition()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "cancelTaskWindowTransition()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10047,7 +10091,8 @@
 
     @Override
     public void cancelTaskThumbnailTransition(int taskId) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskThumbnailTransition()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "cancelTaskThumbnailTransition()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10066,7 +10111,7 @@
 
     @Override
     public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
-        enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
+        enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
         try {
             final TaskRecord task;
@@ -10119,12 +10164,13 @@
 
     @Override
     public void removeStack(int stackId) {
-        enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "removeStack()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
                 final ActivityStack stack = mStackSupervisor.getStack(stackId);
                 if (stack == null) {
+                    Slog.w(TAG, "removeStack: No stack with id=" + stackId);
                     return;
                 }
                 if (!stack.isActivityTypeStandardOrUndefined()) {
@@ -10144,7 +10190,8 @@
      */
     @Override
     public void removeStacksInWindowingModes(int[] windowingModes) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksInWindowingModes()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "removeStacksInWindowingModes()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -10157,7 +10204,8 @@
 
     @Override
     public void removeStacksWithActivityTypes(int[] activityTypes) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksWithActivityTypes()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "removeStacksWithActivityTypes()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -10186,7 +10234,7 @@
 
     @Override
     public boolean removeTask(int taskId) {
-        enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()");
+        enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeTask()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -10229,11 +10277,7 @@
                 Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
                 return;
             }
-            final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked();
-            if (prev != null) {
-                task.setTaskToReturnTo(prev);
-            }
-            mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options, "moveTaskToFront",
+            mStackSupervisor.findTaskToMoveToFront(task, flags, options, "moveTaskToFront",
                     false /* forceNonResizable */);
 
             final ActivityRecord topActivity = task.getTopActivity();
@@ -10318,7 +10362,8 @@
             }
             // TODO(multi-display): Have the caller pass in the windowing mode and activity type.
             final ActivityStack stack = display.createStack(
-                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /*onTop*/);
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+                    ON_TOP);
             return (stack != null) ? stack.mStackId : INVALID_STACK_ID;
         }
     }
@@ -10366,7 +10411,7 @@
 
     @Override
     public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -10402,7 +10447,7 @@
 
     @Override
     public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10453,7 +10498,7 @@
     @Override
     public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
             Rect initialBounds) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10492,12 +10537,16 @@
      */
     @Override
     public void dismissSplitScreenMode(boolean toTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
                 final ActivityStack stack =
                         mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
+                if (stack == null) {
+                    Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found.");
+                    return;
+                }
                 if (toTop) {
                     mStackSupervisor.resizeStackLocked(stack, null /* destBounds */,
                             null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
@@ -10520,14 +10569,14 @@
      */
     @Override
     public void dismissPip(boolean animate, int animationDuration) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
                 final PinnedActivityStack stack =
                         mStackSupervisor.getDefaultDisplay().getPinnedStack();
-
                 if (stack == null) {
+                    Slog.w(TAG, "dismissPip: pinned stack not found.");
                     return;
                 }
                 if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
@@ -10557,7 +10606,8 @@
      */
     @Override
     public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTopActivityToPinnedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "moveTopActivityToPinnedStack()");
         synchronized (this) {
             if (!mSupportsPictureInPicture) {
                 throw new IllegalStateException("moveTopActivityToPinnedStack:"
@@ -10576,13 +10626,14 @@
     @Override
     public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode,
             boolean preserveWindows, boolean animate, int animationDuration) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
                 if (animate) {
                     final PinnedActivityStack stack = mStackSupervisor.getStack(stackId);
                     if (stack == null) {
+                        Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
                         return;
                     }
                     if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
@@ -10611,8 +10662,7 @@
     public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,
             Rect tempDockedTaskInsetBounds,
             Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) {
-        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
-                "resizeDockedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeDockedStack()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10627,8 +10677,7 @@
 
     @Override
     public void resizePinnedStack(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
-        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
-                "resizePinnedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizePinnedStack()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10687,7 +10736,7 @@
 
     @Override
     public List<StackInfo> getAllStackInfos() {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10700,7 +10749,7 @@
 
     @Override
     public StackInfo getStackInfo(int windowingMode, int activityType) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10743,6 +10792,20 @@
         }
     }
 
+    @Override
+    public void updateLockTaskFeatures(int userId, int flags) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != 0 && callingUid != SYSTEM_UID) {
+            enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
+                    "updateLockTaskFeatures()");
+        }
+        synchronized (this) {
+            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Allowing features " + userId + ":0x" +
+                    Integer.toHexString(flags));
+            mLockTaskController.updateLockTaskFeatures(userId, flags);
+        }
+    }
+
     private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isAppPinning) {
         if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task);
         if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
@@ -11861,8 +11924,7 @@
 
     @Override
     public void appNotRespondingViaProvider(IBinder connection) {
-        enforceCallingPermission(
-                android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()");
+        enforceCallingPermission(REMOVE_TASKS, "appNotRespondingViaProvider()");
 
         final ContentProviderConnection conn = (ContentProviderConnection) connection;
         if (conn == null) {
@@ -12330,6 +12392,9 @@
                     + android.Manifest.permission.SHUTDOWN);
         }
 
+        // TODO: Where should the corresponding '1' (start) write go?
+        StatsLog.write(StatsLog.DEVICE_ON_STATUS_CHANGED, 0);
+
         boolean timedout = false;
 
         synchronized(this) {
@@ -13512,6 +13577,7 @@
                     stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
                             sourcePkg != null ? sourcePkg : rec.key.packageName);
                 pkg.noteWakeupAlarmLocked(tag);
+                StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid);
             }
         }
     }
@@ -13892,6 +13958,9 @@
                     com.android.internal.R.string.config_appsNotReportingCrashes));
             mUserController.mUserSwitchUiEnabled = !res.getBoolean(
                     com.android.internal.R.bool.config_customUserSwitchUi);
+            mUserController.mMaxRunningUsers = res.getInteger(
+                    com.android.internal.R.integer.config_multiuserMaxRunningUsers);
+
             if ((globalConfig.uiMode & UI_MODE_TYPE_TELEVISION) == UI_MODE_TYPE_TELEVISION) {
                 mFullscreenThumbnailScale = (float) res
                     .getInteger(com.android.internal.R.integer.thumbnail_width_tv) /
@@ -14083,6 +14152,23 @@
             }
             mStackSupervisor.resumeFocusedStackTopActivityLocked();
             mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
+
+            BinderInternal.nSetBinderProxyCountEnabled(true);
+            BinderInternal.setBinderProxyCountCallback(
+                    new BinderInternal.BinderProxyLimitListener() {
+                        @Override
+                        public void onLimitReached(int uid) {
+                            Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
+                                    + Process.myUid());
+                            if (uid == Process.SYSTEM_UID) {
+                                Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
+                            } else {
+                                killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+                                        "Too many Binders sent to SYSTEM");
+                            }
+                        }
+                    }, mHandler);
+
             traceLog.traceEnd(); // ActivityManagerStartApps
             traceLog.traceEnd(); // PhaseActivityManagerReady
         }
@@ -14239,12 +14325,12 @@
                 }
             }
             sb.append("\n");
-            if (info.crashInfo != null && info.crashInfo.stackTrace != null) {
-                sb.append(info.crashInfo.stackTrace);
+            if (info.hasStackTrace()) {
+                sb.append(info.getStackTrace());
                 sb.append("\n");
             }
-            if (info.message != null) {
-                sb.append(info.message);
+            if (info.getViolationDetails() != null) {
+                sb.append(info.getViolationDetails());
                 sb.append("\n");
             }
 
@@ -14789,7 +14875,7 @@
     /**
      * Wrapper function to print out debug data filtered by specified arguments.
     */
-    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
         boolean dumpAll = false;
@@ -14798,7 +14884,6 @@
         boolean dumpCheckinFormat = false;
         boolean dumpVisibleStacksOnly = false;
         boolean dumpFocusedStackOnly = false;
-        boolean useProto = false;
         String dumpPackage = null;
 
         int opti = 0;
@@ -14832,8 +14917,6 @@
             } else if ("-h".equals(opt)) {
                 ActivityManagerShellCommand.dumpHelp(pw, true);
                 return;
-            } else if ("--proto".equals(opt)) {
-                useProto = true;
             } else {
                 pw.println("Unknown argument: " + opt + "; use -h for help");
             }
@@ -14900,6 +14983,19 @@
                         mRecentTasks.dump(pw, true /* dumpAll */, dumpPackage);
                     }
                 }
+            } else if ("binder-proxies".equals(cmd)) {
+                if (opti >= args.length) {
+                    dumpBinderProxiesCounts(pw, BinderInternal.nGetBinderProxyPerUidCounts(),
+                            "Counts of Binder Proxies held by SYSTEM");
+                } else {
+                    String uid = args[opti];
+                    opti++;
+                    // Ensure Binder Proxy Count is as up to date as possible
+                    System.gc();
+                    System.runFinalization();
+                    System.gc();
+                    pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid)));
+                }
             } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
                 String[] newArgs;
                 String name;
@@ -15418,6 +15514,34 @@
         return printed;
     }
 
+    boolean dumpBinderProxiesCounts(PrintWriter pw, SparseIntArray counts, String header) {
+        if(counts != null) {
+            pw.println(header);
+            for (int i = 0; i < counts.size(); i++) {
+                final int uid = counts.keyAt(i);
+                final int binderCount = counts.valueAt(i);
+                pw.print("    UID ");
+                pw.print(uid);
+                pw.print(", binder count = ");
+                pw.print(binderCount);
+                pw.print(", package(s)= ");
+                final String[] pkgNames = mContext.getPackageManager().getPackagesForUid(uid);
+                if (pkgNames != null) {
+                    for (int j = 0; j < pkgNames.length; j++) {
+                        pw.print(pkgNames[j]);
+                        pw.print("; ");
+                    }
+                } else {
+                    pw.print("NO PACKAGE NAME FOUND");
+                }
+                pw.println();
+            }
+            pw.println();
+            return true;
+        }
+        return false;
+    }
+
     void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
@@ -15623,7 +15747,8 @@
             }
             pw.println("  mPreviousProcess: " + mPreviousProcess);
         }
-        if (dumpAll) {
+        if (dumpAll && (mPreviousProcess == null || dumpPackage == null
+                || mPreviousProcess.pkgList.containsKey(dumpPackage))) {
             StringBuilder sb = new StringBuilder(128);
             sb.append("  mPreviousProcessVisibleTime: ");
             TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb);
@@ -15642,7 +15767,9 @@
             mStackSupervisor.dumpDisplayConfigs(pw, "  ");
         }
         if (dumpAll) {
-            pw.println("  mConfigWillChange: " + getFocusedStack().mConfigWillChange);
+            if (dumpPackage == null) {
+                pw.println("  mConfigWillChange: " + getFocusedStack().mConfigWillChange);
+            }
             if (mCompatModePackages.getPackages().size() > 0) {
                 boolean printed = false;
                 for (Map.Entry<String, Integer> entry
@@ -15722,8 +15849,8 @@
                 pw.println("  mRunningVoice=" + mRunningVoice);
                 pw.println("  mVoiceWakeLock" + mVoiceWakeLock);
             }
+            pw.println("  mVrController=" + mVrController);
         }
-        pw.println("  mVrController=" + mVrController);
         if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
                 || mOrigWaitForDebugger) {
             if (dumpPackage == null || dumpPackage.equals(mDebugApp)
@@ -18060,8 +18187,7 @@
     // =========================================================
 
     @Override
-    public List<ActivityManager.RunningServiceInfo> getServices(int maxNum,
-            int flags) {
+    public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags) {
         enforceNotIsolatedCaller("getServices");
 
         final int callingUid = Binder.getCallingUid();
@@ -20005,7 +20131,7 @@
 
     @Override
     public StackInfo getFocusedStackInfo() throws RemoteException {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -20045,7 +20171,8 @@
     @Override
     // TODO: API should just be about changing windowing modes...
     public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "moveTasksToFullscreenStack()");
         synchronized (this) {
             final long origId = Binder.clearCallingIdentity();
             try {
diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java
index f03d2d5..f942265 100644
--- a/com/android/server/am/ActivityManagerShellCommand.java
+++ b/com/android/server/am/ActivityManagerShellCommand.java
@@ -222,6 +222,8 @@
                     return runSetInactive(pw);
                 case "get-inactive":
                     return runGetInactive(pw);
+                case "set-standby-bucket":
+                    return runSetStandbyBucket(pw);
                 case "send-trim-memory":
                     return runSendTrimMemory(pw);
                 case "display":
@@ -1824,6 +1826,27 @@
         return 0;
     }
 
+    int runSetStandbyBucket(PrintWriter pw) throws RemoteException {
+        int userId = UserHandle.USER_CURRENT;
+
+        String opt;
+        while ((opt=getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        String packageName = getNextArgRequired();
+        String value = getNextArgRequired();
+
+        IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
+                Context.USAGE_STATS_SERVICE));
+        usm.setAppStandbyBucket(packageName, Integer.parseInt(value), userId);
+        return 0;
+    }
+
     int runGetInactive(PrintWriter pw) throws RemoteException {
         int userId = UserHandle.USER_CURRENT;
 
@@ -2282,7 +2305,7 @@
     int runWrite(PrintWriter pw) {
         mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerUidObserver()");
-        mInternal.mRecentTasks.flush();
+        mInternal.getRecentTasks().flush();
         pw.println("All tasks persisted.");
         return 0;
     }
@@ -2571,6 +2594,8 @@
             pw.println("      Sets the inactive state of an app.");
             pw.println("  get-inactive [--user <USER_ID>] <PACKAGE>");
             pw.println("      Returns the inactive state of an app.");
+            pw.println("  set-standby-bucket [--user <USER_ID>] <PACKAGE> <BUCKET>");
+            pw.println("      Puts an app in the standby bucket.");
             pw.println("  send-trim-memory [--user <USER_ID>] <PROCESS>");
             pw.println("          [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]");
             pw.println("      Send a memory trim event to a <PROCESS>.  May also supply a raw trim int level.");
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 2c72a4d..2f0b649 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -165,6 +165,7 @@
 import android.view.IApplicationToken;
 import android.view.WindowManager.LayoutParams;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
@@ -203,7 +204,6 @@
     private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
 
     private static final boolean SHOW_ACTIVITY_START_TIME = true;
-    private static final String RECENTS_PACKAGE_NAME = "com.android.systemui.recents";
 
     private static final String ATTR_ID = "id";
     private static final String TAG_INTENT = "intent";
@@ -232,7 +232,9 @@
     final String processName; // process where this component wants to run
     final String taskAffinity; // as per ActivityInfo.taskAffinity
     final boolean stateNotNeeded; // As per ActivityInfo.flags
-    boolean fullscreen; // covers the full screen?
+    boolean fullscreen; // The activity is opaque and fills the entire space of this task.
+    // TODO: See if it possible to combine this with the fullscreen field.
+    final boolean hasWallpaper; // Has a wallpaper window as a background.
     final boolean noDisplay;  // activity is not displayed?
     private final boolean componentSpecified;  // did caller specify an explicit component?
     final boolean rootVoiceInteraction;  // was this the root activity of a voice interaction?
@@ -274,6 +276,7 @@
     ActivityState state;    // current state we are in
     Bundle  icicle;         // last saved activity state
     PersistableBundle persistentState; // last persistently saved activity state
+    // TODO: See if this is still needed.
     boolean frontOfTask;    // is this the root activity of its task?
     boolean launchFailed;   // set if a launched failed, to abort on 2nd try
     boolean haveState;      // have we gotten the last activity state?
@@ -883,9 +886,14 @@
 
         Entry ent = AttributeCache.instance().get(packageName,
                 realTheme, com.android.internal.R.styleable.Window, userId);
-        fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array);
-        noDisplay = ent != null && ent.array.getBoolean(
-                com.android.internal.R.styleable.Window_windowNoDisplay, false);
+        if (ent != null) {
+            fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array);
+            hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
+            noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+        } else {
+            hasWallpaper = false;
+            noDisplay = false;
+        }
 
         setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord);
 
@@ -1049,7 +1057,7 @@
                 // We only allow home activities to be resizeable if they explicitly requested it.
                 info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
             }
-        } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
+        } else if (service.getRecentTasks().isRecentsComponent(realActivity, appInfo.uid)) {
             activityType = ACTIVITY_TYPE_RECENTS;
         } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
                 && canLaunchAssistActivity(launchedFromPackage)) {
@@ -1553,17 +1561,7 @@
             return false;
         }
 
-        boolean isVisible = !behindFullscreenActivity || mLaunchTaskBehind;
-
-        if (service.mSupportsLeanbackOnly && isVisible && isActivityTypeRecents()) {
-            // On devices that support leanback only (Android TV), Recents activity can only be
-            // visible if the home stack is the focused stack or we are in split-screen mode.
-            final ActivityDisplay display = getDisplay();
-            boolean hasSplitScreenStack = display != null && display.hasSplitScreenPrimaryStack();
-            isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack());
-        }
-
-        return isVisible;
+        return !behindFullscreenActivity || mLaunchTaskBehind;
     }
 
     void makeVisibleIfNeeded(ActivityRecord starting) {
@@ -2065,7 +2063,7 @@
             final File iconFile = new File(TaskPersister.getUserImagesDir(task.userId),
                     iconFilename);
             final String iconFilePath = iconFile.getAbsolutePath();
-            service.mRecentTasks.saveImage(icon, iconFilePath);
+            service.getRecentTasks().saveImage(icon, iconFilePath);
             _taskDescription.setIconFilename(iconFilePath);
         }
         taskDescription = _taskDescription;
@@ -2812,7 +2810,7 @@
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         writeIdentifierToProto(proto, IDENTIFIER);
         proto.write(STATE, state.toString());
         proto.write(VISIBLE, visible);
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index 941c371..ba41bd4 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -16,13 +16,10 @@
 
 package com.android.server.am;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -102,12 +99,13 @@
 import android.app.AppGlobals;
 import android.app.IActivityController;
 import android.app.ResultInfo;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
@@ -346,7 +344,6 @@
     private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
     private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
     private final Rect mTmpRect2 = new Rect();
-    private final Point mTmpSize = new Point();
 
     /** Run all ActivityStacks through this */
     protected final ActivityStackSupervisor mStackSupervisor;
@@ -489,26 +486,23 @@
             activityType = ACTIVITY_TYPE_STANDARD;
         }
         final ActivityDisplay display = getDisplay();
-        if (display != null) {
-            if (activityType == ACTIVITY_TYPE_STANDARD
+        if (display != null && activityType == ACTIVITY_TYPE_STANDARD
                     && windowingMode == WINDOWING_MODE_UNDEFINED) {
-                // Standard activity types will mostly take on the windowing mode of the display if
-                // one isn't specified, so look-up a compatible stack based on the display's
-                // windowing mode.
-                windowingMode = display.getWindowingMode();
-            }
-            windowingMode =
-                    display.updateWindowingModeForSplitScreenIfNeeded(windowingMode, activityType);
+            // Standard activity types will mostly take on the windowing mode of the display if one
+            // isn't specified, so look-up a compatible stack based on the display's windowing mode.
+            windowingMode = display.getWindowingMode();
         }
         return super.isCompatible(windowingMode, activityType);
     }
 
     /** Adds the stack to specified display and calls WindowManager to do the same. */
     void reparent(ActivityDisplay activityDisplay, boolean onTop) {
+        // TODO: We should probably resolve the windowing mode for the stack on the new display here
+        // so that it end up in a compatible mode in the new display. e.g. split-screen secondary.
         removeFromDisplay();
         mTmpRect2.setEmpty();
         postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
-        adjustFocusToNextFocusableStackLocked("reparent", true /* allowFocusSelf */);
+        adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */);
         mStackSupervisor.resumeFocusedStackTopActivityLocked();
         // Update visibility of activities before notifying WM. This way it won't try to resize
         // windows that are no longer visible.
@@ -837,6 +831,12 @@
         return mDisplayId == DEFAULT_DISPLAY;
     }
 
+    private boolean returnsToHomeStack() {
+        return !inMultiWindowMode()
+                && !mTaskHistory.isEmpty()
+                && mTaskHistory.get(0).returnsToHomeStack();
+    }
+
     void moveToFront(String reason) {
         moveToFront(reason, null);
     }
@@ -850,6 +850,12 @@
             return;
         }
 
+        if (!isActivityTypeHome() && returnsToHomeStack()) {
+            // Make sure the home stack is behind this stack since that is where we should return to
+            // when this stack is no longer visible.
+            mStackSupervisor.moveHomeStackToFront(reason + " returnToHome");
+        }
+
         getDisplay().positionChildAtTop(this);
         mStackSupervisor.setFocusStackUnchecked(reason, this);
         if (task != null) {
@@ -1496,25 +1502,17 @@
         }
     }
 
-    /** Returns true if the stack contains a fullscreen task. */
-    private boolean hasFullscreenTask() {
-        for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
-            final TaskRecord task = mTaskHistory.get(i);
-            if (task.mFullscreen) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * Returns true if the stack is translucent and can have other contents visible behind it if
      * needed. A stack is considered translucent if it don't contain a visible or
      * starting (about to be visible) activity that is fullscreen (opaque).
      * @param starting The currently starting activity or null if there is none.
-     * @param stackBehind The stack directly behind this one.
      */
-    private boolean isStackTranslucent(ActivityRecord starting, ActivityStack stackBehind) {
+    @VisibleForTesting
+    boolean isStackTranslucent(ActivityRecord starting) {
+        if (!isAttached() || mForceHidden) {
+            return true;
+        }
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1533,21 +1531,11 @@
                     continue;
                 }
 
-                if (r.fullscreen) {
+                if (r.fullscreen || r.hasWallpaper) {
                     // Stack isn't translucent if it has at least one fullscreen activity
                     // that is visible.
                     return false;
                 }
-
-                final boolean stackBehindHomeOrRecent = stackBehind != null
-                        && stackBehind.isHomeOrRecentsStack();
-                if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()
-                        && !stackBehindHomeOrRecent && !isActivityTypeAssistant()) {
-                    // Stack isn't translucent if it's top activity should have the home stack
-                    // behind it and the stack currently behind it isn't the home or recents stack
-                    // or the assistant stack.
-                    return false;
-                }
             }
         }
         return true;
@@ -1572,127 +1560,66 @@
         if (!isAttached() || mForceHidden) {
             return false;
         }
-
-        final ActivityDisplay display = getDisplay();
-        if (isTopStackOnDisplay() || mStackSupervisor.isFocusedStack(this)) {
+        if (mStackSupervisor.isFocusedStack(this)) {
             return true;
         }
 
-        final int stackIndex = display.getIndexOf(this);
-
-        // Check position and visibility of this stack relative to the front stack on its display.
-        final ActivityStack topStack = getDisplay().getTopStack();
-        final int windowingMode = getWindowingMode();
-        final int activityType = getActivityType();
-
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            // If the assistant stack is focused and translucent, then the docked stack is always
-            // visible
-            if (topStack.isActivityTypeAssistant()) {
-                return topStack.isStackTranslucent(starting, this);
-            }
-            return true;
-        }
-
-        // Set home stack to invisible when it is below but not immediately below the docked stack
-        // A case would be if recents stack exists but has no tasks and is below the docked stack
-        // and home stack is below recents
-        if (activityType == ACTIVITY_TYPE_HOME) {
-            final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
-            int dockedStackIndex = display.getIndexOf(splitScreenStack);
-            if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
-                return false;
-            }
-        }
-
-        // Find the first stack behind front stack that actually got something visible.
-        int stackBehindTopIndex = display.getIndexOf(topStack) - 1;
-        while (stackBehindTopIndex >= 0 &&
-                display.getChildAt(stackBehindTopIndex).topRunningActivityLocked() == null) {
-            stackBehindTopIndex--;
-        }
-        final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0)
-                ? display.getChildAt(stackBehindTopIndex) : null;
-        int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED;
-        int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED;
-        if (stackBehindTop != null) {
-            stackBehindTopWindowingMode = stackBehindTop.getWindowingMode();
-            stackBehindTopActivityType = stackBehindTop.getActivityType();
-        }
-
-        final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop();
-        if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) {
-            if (this == stackBehindTop) {
-                // Stacks directly behind the docked or pinned stack are always visible.
-                return true;
-            } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
-                // Otherwise, this stack can also be visible if it is directly behind a docked stack
-                // or translucent assistant stack behind an always-on-top top-most stack
-                if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-                    return true;
-                } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) {
-                    return stackBehindTop.isStackTranslucent(starting, this);
-                }
-            }
-        }
-
-        if (topStack.isBackdropToTranslucentActivity()
-                && topStack.isStackTranslucent(starting, stackBehindTop)) {
-            // Stacks behind the fullscreen or assistant stack with a translucent activity are
-            // always visible so they can act as a backdrop to the translucent activity.
-            // For example, dialog activities
-            if (stackIndex == stackBehindTopIndex) {
-                return true;
-            }
-            if (stackBehindTopIndex >= 0) {
-                if ((stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                        || stackBehindTopWindowingMode == WINDOWING_MODE_PINNED)
-                        && stackIndex == (stackBehindTopIndex - 1)) {
-                    // The stack behind the docked or pinned stack is also visible so we can have a
-                    // complete backdrop to the translucent activity when the docked stack is up.
-                    return true;
-                }
-            }
-        }
-
-        if (isOnHomeDisplay()) {
-            // Visibility of any stack on default display should have been determined by the
-            // conditions above.
+        final ActivityRecord top = topRunningActivityLocked();
+        if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) {
+            // Shouldn't be visible if you don't have any running activities, not starting one, and
+            // not the top stack on display.
             return false;
         }
 
-        final int stackCount = display.getChildCount();
-        for (int i = stackIndex + 1; i < stackCount; i++) {
-            final ActivityStack stack = display.getChildAt(i);
-
-            if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
-                continue;
-            }
-
-            if (!stack.isDynamicStacksVisibleBehindAllowed()) {
-                // These stacks can't have any dynamic stacks visible behind them.
-                return false;
-            }
-
-            if (!stack.isStackTranslucent(starting, null /* stackBehind */)) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    private boolean isBackdropToTranslucentActivity() {
-        if (isActivityTypeAssistant()) {
-            return true;
-        }
+        final ActivityDisplay display = getDisplay();
+        boolean gotOpaqueSplitScreenPrimary = false;
+        boolean gotOpaqueSplitScreenSecondary = false;
         final int windowingMode = getWindowingMode();
-        return windowingMode == WINDOWING_MODE_FULLSCREEN
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-    }
+        for (int i = display.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack other = display.getChildAt(i);
+            if (other == this) {
+                // Should be visible if there is no other stack occluding it.
+                return true;
+            }
 
-    private boolean isDynamicStacksVisibleBehindAllowed() {
-        return isActivityTypeAssistant() || getWindowingMode() == WINDOWING_MODE_PINNED;
+            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)) {
+                    // Can be visible behind a translucent fullscreen stack.
+                    continue;
+                }
+                return false;
+            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                    && !gotOpaqueSplitScreenPrimary) {
+                gotOpaqueSplitScreenPrimary =
+                        !other.isStackTranslucent(starting);
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                        && gotOpaqueSplitScreenPrimary) {
+                    // Can not be visible behind another opaque stack in split-screen-primary mode.
+                    return false;
+                }
+            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                    && !gotOpaqueSplitScreenSecondary) {
+                gotOpaqueSplitScreenSecondary =
+                        !other.isStackTranslucent(starting);
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                        && gotOpaqueSplitScreenSecondary) {
+                    // Can not be visible behind another opaque stack in split-screen-secondary mode.
+                    return false;
+                }
+            }
+            if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
+                // Can not be visible if we are in split-screen windowing mode and both halves of
+                // the screen are opaque.
+                return false;
+            }
+        }
+
+        // Well, nothing is stopping you from being visible...
+        return true;
     }
 
     final int rankTaskLayers(int baseLayer) {
@@ -1713,6 +1640,7 @@
      * Make sure that all activities that need to be visible (that is, they
      * currently can be seen by the user) actually are.
      */
+    // TODO: Should be re-worked based on the fact that each task as a stack in most cases.
     final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges,
             boolean preserveWindows) {
         mTopActivityOccludesKeyguard = false;
@@ -1757,7 +1685,7 @@
                             visibleIgnoringKeyguard, isTop);
                     if (visibleIgnoringKeyguard) {
                         behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible,
-                                behindFullscreenActivity, task, r);
+                                behindFullscreenActivity, r);
                     }
                     if (reallyVisible) {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
@@ -1816,16 +1744,6 @@
                     // show activities in the next application stack behind them vs. another
                     // task in the home stack like recents.
                     behindFullscreenActivity = true;
-                } else if (windowingMode == WINDOWING_MODE_FULLSCREEN
-                        || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-                    if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task
-                            + " returning to non-application type=" + task.getTaskToReturnTo());
-                    // Once we reach a fullscreen stack task that has a running activity and should
-                    // return to another stack task, then no other activities behind that one should
-                    // be visible.
-                    if (task.topRunningActivityLocked() != null && !task.returnsToStandardTask()) {
-                        behindFullscreenActivity = true;
-                    }
                 }
             }
 
@@ -1861,24 +1779,6 @@
     }
 
     /**
-     * Returns true if we try to maintain focus in the current stack when the top activity finishes.
-     */
-    private boolean keepFocusInStackIfPossible() {
-        final int windowingMode = getWindowingMode();
-        return windowingMode == WINDOWING_MODE_FREEFORM
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_PINNED;
-    }
-
-    /**
-     * Returns true if the top task in the task is allowed to return home when finished and
-     * there are other tasks in the stack.
-     */
-    boolean allowTopTaskToReturnHome() {
-        return !inPinnedWindowingMode();
-    }
-
-    /**
      * @return the top most visible activity that wants to dismiss Keyguard
      */
     ActivityRecord getTopDismissingKeyguardActivity() {
@@ -2039,18 +1939,13 @@
     }
 
     private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
-            TaskRecord task, ActivityRecord r) {
+            ActivityRecord r) {
         if (r.fullscreen) {
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
                         + " stackInvisible=" + stackInvisible
                         + " behindFullscreenActivity=" + behindFullscreenActivity);
             // At this point, nothing else needs to be shown in this task.
             behindFullscreenActivity = true;
-        } else if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()) {
-            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
-                    + " stackInvisible=" + stackInvisible
-                    + " behindFullscreenActivity=" + behindFullscreenActivity);
-            behindFullscreenActivity = true;
         }
         return behindFullscreenActivity;
     }
@@ -2209,7 +2104,7 @@
         final boolean hasRunningActivity = next != null;
 
         // TODO: Maybe this entire condition can get removed?
-        if (hasRunningActivity && getDisplay() == null) {
+        if (hasRunningActivity && !isAttached()) {
             return false;
         }
 
@@ -2239,28 +2134,6 @@
             return false;
         }
 
-        final TaskRecord nextTask = next.getTask();
-        final TaskRecord prevTask = prev != null ? prev.getTask() : null;
-        if (prevTask != null && prevTask.getStack() == this &&
-                prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) {
-            if (DEBUG_STACK)  mStackSupervisor.validateTopActivitiesLocked();
-            if (prevTask == nextTask) {
-                prevTask.setFrontOfTask();
-            } else if (prevTask != topTask()) {
-                // This task is going away but it was supposed to return to the home stack.
-                // Now the task above it has to return to the home task instead.
-                final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
-                mTaskHistory.get(taskNdx).setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            } else if (!isOnHomeDisplay()) {
-                return false;
-            } else if (!isActivityTypeHome()){
-                if (DEBUG_STATES) Slog.d(TAG_STATES,
-                        "resumeTopActivityLocked: Launching home next");
-                return isOnHomeDisplay() &&
-                        mStackSupervisor.resumeHomeStackTask(prev, "prevFinished");
-            }
-        }
-
         // If we are sleeping, and there is no resumed activity, and the top
         // activity is paused, well that is the state we want.
         if (shouldSleepOrShutDownActivities()
@@ -2644,7 +2517,7 @@
 
     private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev,
             ActivityOptions options, String reason) {
-        if (adjustFocusToNextFocusableStackLocked(reason)) {
+        if (adjustFocusToNextFocusableStack(reason)) {
             // Try to move focus to the next visible stack with a running activity if this
             // stack is not covering the entire screen or is on a secondary display (with no home
             // stack).
@@ -2718,7 +2591,6 @@
     }
 
     private void insertTaskAtTop(TaskRecord task, ActivityRecord starting) {
-        updateTaskReturnToForTopInsertion(task);
         // TODO: Better place to put all the code below...may be addTask...
         mTaskHistory.remove(task);
         // Now put task at top.
@@ -2729,57 +2601,6 @@
                 true /* includingParents */);
     }
 
-    /**
-     * Updates the {@param task}'s return type before it is moved to the top.
-     */
-    private void updateTaskReturnToForTopInsertion(TaskRecord task) {
-        boolean isLastTaskOverHome = false;
-        // If the moving task is over the home or assistant stack, transfer its return type to next
-        // task so that they return to the same stack
-        if (task.isOverHomeStack() || task.isOverAssistantStack()) {
-            final TaskRecord nextTask = getNextTask(task);
-            if (nextTask != null) {
-                nextTask.setTaskToReturnTo(task.getTaskToReturnTo());
-            } else {
-                isLastTaskOverHome = true;
-            }
-        }
-
-        // If this is not on the default display, then just set the return type to application
-        if (!isOnHomeDisplay()) {
-            task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-            return;
-        }
-
-        final ActivityStack lastStack = mStackSupervisor.getLastStack();
-
-        // If there is no last task, do not set task to return to
-        if (lastStack == null) {
-            return;
-        }
-
-        // If the task was launched from the assistant stack, set the return type to assistant
-        if (lastStack.isActivityTypeAssistant()) {
-            task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
-            return;
-        }
-
-        // If this is being moved to the top by another activity or being launched from the home
-        // activity, set mTaskToReturnTo accordingly.
-        final boolean fromHomeOrRecents = lastStack.isHomeOrRecentsStack();
-        final TaskRecord topTask = lastStack.topTask();
-        if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) {
-            // If it's a last task over home - we default to keep its return to type not to
-            // make underlying task focused when this one will be finished.
-            int returnToType = isLastTaskOverHome
-                    ? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD;
-            if (fromHomeOrRecents && allowTopTaskToReturnHome()) {
-                returnToType = topTask == null ? ACTIVITY_TYPE_HOME : topTask.getActivityType();
-            }
-            task.setTaskToReturnTo(returnToType);
-        }
-    }
-
     final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
             boolean newTask, boolean keepCurTransition, ActivityOptions options) {
         TaskRecord rTask = r.getTask();
@@ -3049,7 +2870,7 @@
                 }
 
                 mWindowContainerController.positionChildAtBottom(
-                        targetTask.getWindowContainerController());
+                        targetTask.getWindowContainerController(), false /* includingParents */);
                 replyChainEnd = -1;
             } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
                 // If the activity should just be removed -- either
@@ -3285,7 +3106,7 @@
     }
 
     /** Returns true if the task is one of the task finishing on-top of the top running task. */
-    boolean isATopFinishingTask(TaskRecord task) {
+    private boolean isATopFinishingTask(TaskRecord task) {
         for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
             final TaskRecord current = mTaskHistory.get(i);
             final ActivityRecord r = current.topRunningActivityLocked();
@@ -3300,7 +3121,7 @@
         return false;
     }
 
-    private void adjustFocusedActivityStackLocked(ActivityRecord r, String reason) {
+    private void adjustFocusedActivityStack(ActivityRecord r, String reason) {
         if (!mStackSupervisor.isFocusedStack(this) ||
                 ((mResumedActivity != r) && (mResumedActivity != null))) {
             return;
@@ -3309,66 +3130,44 @@
         final ActivityRecord next = topRunningActivityLocked();
         final String myReason = reason + " adjustFocus";
 
-        if (next != r) {
-            if (next != null && keepFocusInStackIfPossible() && isFocusable()) {
-                // For freeform, docked, and pinned stacks we always keep the focus within the
-                // stack as long as there is a running activity.
-                return;
-            } else {
-                // Task is not guaranteed to be non-null. For example, destroying the
-                // {@link ActivityRecord} will disassociate the task from the activity.
-                final TaskRecord task = r.getTask();
-
-                if (task == null) {
-                    throw new IllegalStateException("activity no longer associated with task:" + r);
-                }
-
-                final boolean isAssistantOrOverAssistant =
-                        task.getStack().isActivityTypeAssistant() || task.isOverAssistantStack();
-                if (r.frontOfTask && isATopFinishingTask(task)
-                        && (task.isOverHomeStack() || isAssistantOrOverAssistant)) {
-                    // For non-fullscreen or assistant stack, we want to move the focus to the next
-                    // visible stack to prevent the home screen from moving to the top and obscuring
-                    // other visible stacks.
-                    if ((!mFullscreen || isAssistantOrOverAssistant)
-                            && adjustFocusToNextFocusableStackLocked(myReason)) {
-                        return;
-                    }
-                    // Move the home stack to the top if this stack is fullscreen or there is no
-                    // other visible stack.
-                    if (task.isOverHomeStack() &&
-                            mStackSupervisor.moveHomeStackTaskToTop(myReason)) {
-                        // Activity focus was already adjusted. Nothing else to do...
-                        return;
-                    }
-                }
-            }
+        if (next == r) {
+            mStackSupervisor.moveFocusableActivityStackToFrontLocked(
+                    mStackSupervisor.topRunningActivityLocked(), myReason);
+            return;
         }
 
-        mStackSupervisor.moveFocusableActivityStackToFrontLocked(
-                mStackSupervisor.topRunningActivityLocked(), myReason);
+        if (next != null && isFocusable()) {
+            // Keep focus in stack if we have a top running activity and are focusable.
+            return;
+        }
+
+        // Task is not guaranteed to be non-null. For example, destroying the
+        // {@link ActivityRecord} will disassociate the task from the activity.
+        final TaskRecord task = r.getTask();
+
+        if (task == null) {
+            throw new IllegalStateException("activity no longer associated with task:" + r);
+        }
+
+        // Move focus to next focusable stack if possible.
+        if (adjustFocusToNextFocusableStack(myReason)) {
+            return;
+        }
+
+        // Whatever...go home.
+        mStackSupervisor.moveHomeStackTaskToTop(myReason);
     }
 
     /** Find next proper focusable stack and make it focused. */
-    private boolean adjustFocusToNextFocusableStackLocked(String reason) {
-        return adjustFocusToNextFocusableStackLocked(reason, false /* allowFocusSelf */);
+    private boolean adjustFocusToNextFocusableStack(String reason) {
+        return adjustFocusToNextFocusableStack(reason, false /* allowFocusSelf */);
     }
 
     /**
      * Find next proper focusable stack and make it focused.
      * @param allowFocusSelf Is the focus allowed to remain on the same stack.
      */
-    private boolean adjustFocusToNextFocusableStackLocked(String reason, boolean allowFocusSelf) {
-        if (isActivityTypeAssistant() && bottomTask() != null
-                && bottomTask().returnsToHomeTask()) {
-            // If the current stack is the assistant stack, then use the return-to type to determine
-            // whether to return to the home screen. This is needed to workaround an issue where
-            // launching a fullscreen task (and subequently returning from that task) will cause
-            // the fullscreen stack to be found as the next focusable stack below, even if the
-            // assistant was launched over home.
-            return mStackSupervisor.moveHomeStackTaskToTop(reason);
-        }
-
+    private boolean adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf) {
         final ActivityStack stack = mStackSupervisor.getNextFocusableStackLocked(
                 allowFocusSelf ? null : this);
         final String myReason = reason + " adjustFocusToNextFocusableStack";
@@ -3378,22 +3177,12 @@
 
         final ActivityRecord top = stack.topRunningActivityLocked();
 
-        if (stack.isHomeOrRecentsStack() && (top == null || !top.visible)) {
+        if (stack.isActivityTypeHome() && (top == null || !top.visible)) {
             // If we will be focusing on the home stack next and its current top activity isn't
-            // visible, then use the task return to value to determine the home task to display
-            // next.
+            // visible, then use the move the home stack task to top to make the activity visible.
             return mStackSupervisor.moveHomeStackTaskToTop(reason);
         }
 
-        if (stack.isActivityTypeAssistant() && top != null
-                && top.getTask().returnsToHomeTask()) {
-            // It is possible for the home stack to not be directly underneath the assistant stack.
-            // For example, the assistant may start an activity in the fullscreen stack. Upon
-            // returning to the assistant stack, we must ensure that the home stack is underneath
-            // when appropriate.
-            mStackSupervisor.moveHomeStackTaskToTop("adjustAssistantReturnToHome");
-        }
-
         stack.moveToFront(myReason);
         return true;
     }
@@ -3408,7 +3197,7 @@
                     if (requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
                             "stop-no-history", false)) {
                         // If {@link requestFinishActivityLocked} returns {@code true},
-                        // {@link adjustFocusedActivityStackLocked} would have been already called.
+                        // {@link adjustFocusedActivityStack} would have been already called.
                         r.resumeKeyDispatchingLocked();
                         return;
                     }
@@ -3420,7 +3209,7 @@
         }
 
         if (r.app != null && r.app.thread != null) {
-            adjustFocusedActivityStackLocked(r, "stopActivity");
+            adjustFocusedActivityStack(r, "stopActivity");
             r.resumeKeyDispatchingLocked();
             try {
                 r.stopped = false;
@@ -3659,7 +3448,7 @@
 
             r.pauseKeyDispatchingLocked();
 
-            adjustFocusedActivityStackLocked(r, "finishActivity");
+            adjustFocusedActivityStack(r, "finishActivity");
 
             finishActivityResultsLocked(r, resultCode, resultData);
 
@@ -3826,7 +3615,21 @@
         }
     }
 
-    final boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) {
+    /** @return true if the stack behind this one is a standard activity type. */
+    boolean inFrontOfStandardStack() {
+        final ActivityDisplay display = getDisplay();
+        if (display == null) {
+            return false;
+        }
+        final int index = display.getIndexOf(this);
+        if (index == 0) {
+            return false;
+        }
+        final ActivityStack stackBehind = display.getChildAt(index - 1);
+        return stackBehind.isActivityTypeStandard();
+    }
+
+    boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) {
         // Basic case: for simple app-centric recents, we need to recreate
         // the task if the affinity has changed.
         if (srec == null || srec.getTask().affinity == null ||
@@ -3838,10 +3641,9 @@
         // of a document, unless simply finishing it will return them to the the
         // correct app behind.
         final TaskRecord task = srec.getTask();
-        if (srec.frontOfTask && task != null && task.getBaseIntent() != null
-                && task.getBaseIntent().isDocument()) {
+        if (srec.frontOfTask && task.getBaseIntent() != null && task.getBaseIntent().isDocument()) {
             // Okay, this activity is at the root of its task.  What to do, what to do...
-            if (!task.returnsToStandardTask()) {
+            if (!inFrontOfStandardStack()) {
                 // Finishing won't return to an application, so we need to recreate.
                 return true;
             }
@@ -4041,11 +3843,6 @@
                                 + " onlyHasTaskOverlays=" + onlyHasTaskOverlays);
             }
 
-            if (mStackSupervisor.isFocusedStack(this) && task == topTask() &&
-                    task.isOverHomeStack()) {
-                mStackSupervisor.moveHomeStackTaskToTop(reason);
-            }
-
             // The following block can be executed multiple times if there is more than one overlay.
             // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup
             // of the task by id and exiting early if not found.
@@ -4567,60 +4364,19 @@
 
         if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId);
 
-        boolean prevIsHome = false;
+        mTaskHistory.remove(tr);
+        mTaskHistory.add(0, tr);
+        updateTaskMovement(tr, false);
 
-        // If true, we should resume the home activity next if the task we are moving to the
-        // back is over the home stack. We force to false if the task we are moving to back
-        // is the home task and we don't want it resumed after moving to the back.
-        final boolean canGoHome = !tr.isActivityTypeHome() && tr.isOverHomeStack();
-        if (canGoHome) {
-            final TaskRecord nextTask = getNextTask(tr);
-            if (nextTask != null) {
-                nextTask.setTaskToReturnTo(tr.getTaskToReturnTo());
-            } else {
-                prevIsHome = true;
-            }
-        }
-
-        boolean requiresMove = mTaskHistory.indexOf(tr) != 0;
-        if (requiresMove) {
-            mTaskHistory.remove(tr);
-            mTaskHistory.add(0, tr);
-            updateTaskMovement(tr, false);
-
-            mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
-            mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController());
-        }
+        mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
+        mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(),
+                true /* includingParents */);
 
         if (inPinnedWindowingMode()) {
             mStackSupervisor.removeStack(this);
             return true;
         }
 
-        // Otherwise, there is an assumption that moving a task to the back moves it behind the
-        // home activity. We make sure here that some activity in the stack will launch home.
-        int numTasks = mTaskHistory.size();
-        for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) {
-            final TaskRecord task = mTaskHistory.get(taskNdx);
-            if (task.isOverHomeStack()) {
-                break;
-            }
-            if (taskNdx == 1) {
-                // Set the last task before tr to go to home.
-                task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            }
-        }
-
-        final TaskRecord task = mResumedActivity != null ? mResumedActivity.getTask() : null;
-        if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) {
-            if (!mService.mBooting && !mService.mBooted) {
-                // Not ready yet!
-                return false;
-            }
-            tr.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-            return mStackSupervisor.resumeHomeStackTask(null, "moveTaskToBack");
-        }
-
         mStackSupervisor.resumeFocusedStackTopActivityLocked();
         return true;
     }
@@ -4851,69 +4607,43 @@
         return didSomething;
     }
 
-    void getTasksLocked(List<RunningTaskInfo> list, int callingUid, boolean allowed) {
+    /**
+     * @return The set of running tasks through {@param tasksOut} that are available to the caller.
+     *         If {@param ignoreActivityType} or {@param ignoreWindowingMode} are not undefined,
+     *         then skip running tasks that match those types.
+     */
+    void getRunningTasks(List<TaskRecord> tasksOut, @ActivityType int ignoreActivityType,
+            @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed) {
         boolean focusedStack = mStackSupervisor.getFocusedStack() == this;
         boolean topTask = true;
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             if (task.getTopActivity() == null) {
+                // Skip if there are no activities in the task
                 continue;
             }
-            ActivityRecord r = null;
-            ActivityRecord top = null;
-            ActivityRecord tmp;
-            int numActivities = 0;
-            int numRunning = 0;
-            final ArrayList<ActivityRecord> activities = task.mActivities;
             if (!allowed && !task.isActivityTypeHome() && task.effectiveUid != callingUid) {
+                // Skip if the caller can't fetch this task
                 continue;
             }
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                tmp = activities.get(activityNdx);
-                if (tmp.finishing) {
-                    continue;
-                }
-                r = tmp;
-
-                // Initialize state for next task if needed.
-                if (top == null || (top.state == ActivityState.INITIALIZING)) {
-                    top = r;
-                    numActivities = numRunning = 0;
-                }
-
-                // Add 'r' into the current task.
-                numActivities++;
-                if (r.app != null && r.app.thread != null) {
-                    numRunning++;
-                }
-
-                if (DEBUG_ALL) Slog.v(
-                    TAG, r.intent.getComponent().flattenToShortString()
-                    + ": task=" + r.getTask());
+            if (ignoreActivityType != ACTIVITY_TYPE_UNDEFINED
+                    && task.getActivityType() == ignoreActivityType) {
+                // Skip ignored activity type
+                continue;
             }
-
-            RunningTaskInfo ci = new RunningTaskInfo();
-            ci.id = task.taskId;
-            ci.stackId = mStackId;
-            ci.baseActivity = r.intent.getComponent();
-            ci.topActivity = top.intent.getComponent();
-            ci.lastActiveTime = task.lastActiveTime;
+            if (ignoreWindowingMode != WINDOWING_MODE_UNDEFINED
+                    && task.getWindowingMode() == ignoreWindowingMode) {
+                // Skip ignored windowing mode
+                continue;
+            }
             if (focusedStack && topTask) {
-                // Give the latest time to ensure foreground task can be sorted
-                // at the first, because lastActiveTime of creating task is 0.
-                ci.lastActiveTime = SystemClock.elapsedRealtime();
+                // For the focused stack top task, update the last stack active time so that it can
+                // be used to determine the order of the tasks (it may not be set for newly created
+                // tasks)
+                task.lastActiveTime = SystemClock.elapsedRealtime();
                 topTask = false;
             }
-
-            if (top.getTask() != null) {
-                ci.description = top.getTask().lastDescription;
-            }
-            ci.numActivities = numActivities;
-            ci.numRunning = numRunning;
-            ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode();
-            ci.resizeMode = task.mResizeMode;
-            ci.configuration.setTo(task.getConfiguration());
-            list.add(ci);
+            tasksOut.add(task);
         }
     }
 
@@ -5057,14 +4787,6 @@
             onActivityRemovedFromStack(record);
         }
 
-        final int taskNdx = mTaskHistory.indexOf(task);
-        final int topTaskNdx = mTaskHistory.size() - 1;
-        if (task.isOverHomeStack() && taskNdx < topTaskNdx) {
-            final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1);
-            if (!nextTask.isOverHomeStack() && !nextTask.isOverAssistantStack()) {
-                nextTask.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            }
-        }
         mTaskHistory.remove(task);
         removeActivitiesFromLRUListLocked(task);
         updateTaskMovement(task, true);
@@ -5094,7 +4816,7 @@
             if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP
                     && mStackSupervisor.isFocusedStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
-                if (mFullscreen || !adjustFocusToNextFocusableStackLocked(myReason)) {
+                if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) {
                     mStackSupervisor.moveHomeStackToFront(myReason);
                 }
             }
@@ -5123,24 +4845,14 @@
         addTask(task, toTop, "createTaskRecord");
         final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController
                 .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
-        if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable()
-                && !isLockscreenShown) {
+        if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
+                && mBounds != null && task.isResizeable() && !isLockscreenShown) {
             task.updateOverrideConfiguration(mBounds);
         }
         task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
         return task;
     }
 
-    boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) {
-        if (!task.inFreeformWindowingMode()) {
-            return false;
-        }
-        mStackSupervisor.getLaunchingTaskPositioner()
-                .updateDefaultBounds(task, mTaskHistory, windowLayout);
-
-        return true;
-    }
-
     ArrayList<TaskRecord> getAllTasks() {
         return new ArrayList<>(mTaskHistory);
     }
@@ -5167,10 +4879,6 @@
         mTaskHistory.add(position, task);
         task.setStack(this);
 
-        if (toTop) {
-            updateTaskReturnToForTopInsertion(task);
-        }
-
         updateTaskMovement(task, toTop);
 
         postAddTask(task, prevStack, schedulePictureInPictureModeChange);
@@ -5263,7 +4971,8 @@
     public String toString() {
         return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
                 + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType())
-                + " mode=" + windowingModeToString(getWindowingMode()) + ", "
+                + " mode=" + windowingModeToString(getWindowingMode())
+                + " visible=" + shouldBeVisible(null /* starting */) + ", "
                 + mTaskHistory.size() + " tasks}";
     }
 
@@ -5290,7 +4999,7 @@
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         proto.write(ID, mStackId);
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index c15b5e2..6ec158e 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -110,7 +110,8 @@
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
 import android.app.WaitResult;
-import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -223,13 +224,6 @@
     // at the top of its container (e.g. stack).
     static final boolean ON_TOP = true;
 
-    // Used to indicate that an objects (e.g. task) removal from its container
-    // (e.g. stack) is due to it moving to another container.
-    static final boolean MOVING = true;
-
-    // Force the focus to change to the stack we are moving a task to..
-    static final boolean FORCE_FOCUS = true;
-
     // Don't execute any calls to resume.
     static final boolean DEFER_RESUME = true;
 
@@ -287,15 +281,19 @@
 
     final ActivityManagerService mService;
 
+    /** The historial list of recent tasks including inactive tasks */
     RecentTasks mRecentTasks;
 
+    /** Helper class to abstract out logic for fetching the set of currently running tasks */
+    private RunningTasks mRunningTasks;
+
     final ActivityStackSupervisorHandler mHandler;
 
     /** Short cut */
     WindowManagerService mWindowManager;
     DisplayManager mDisplayManager;
 
-    LaunchingTaskPositioner mTaskPositioner = new LaunchingTaskPositioner();
+    private final LaunchingBoundsController mLaunchingBoundsController;
 
     /** Counter for next free stack ID to use for dynamic activity stacks. */
     private int mNextFreeStackId = 0;
@@ -405,6 +403,7 @@
      * object each time.
      */
     private final Rect tempRect = new Rect();
+    private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
 
     // The default minimal size that will be used if the activity doesn't specify its minimal size.
     // It will be calculated when the default display gets added.
@@ -573,8 +572,12 @@
     public ActivityStackSupervisor(ActivityManagerService service, Looper looper) {
         mService = service;
         mHandler = new ActivityStackSupervisorHandler(looper);
+        mRunningTasks = createRunningTasks();
         mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
         mKeyguardController = new KeyguardController(service, this);
+
+        mLaunchingBoundsController = new LaunchingBoundsController();
+        mLaunchingBoundsController.registerDefaultPositioners(this);
     }
 
     void setRecentTasks(RecentTasks recentTasks) {
@@ -582,6 +585,11 @@
         mRecentTasks.registerCallback(this);
     }
 
+    @VisibleForTesting
+    RunningTasks createRunningTasks() {
+        return new RunningTasks();
+    }
+
     /**
      * At the time when the constructor runs, the power manager has not yet been
      * initialized.  So we initialize our wakelocks afterwards.
@@ -685,10 +693,6 @@
             return false;
         }
 
-        if (prev != null) {
-            prev.getTask().setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-        }
-
         mHomeStack.moveHomeStackTaskToTop();
         ActivityRecord r = getHomeActivity();
         final String myReason = reason + " resumeHomeStackTask";
@@ -1160,43 +1164,12 @@
         return null;
     }
 
-    void getTasksLocked(int maxNum, List<RunningTaskInfo> list, int callingUid, boolean allowed) {
-        // Gather all of the running tasks for each stack into runningTaskLists.
-        ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists = new ArrayList<>();
-        final int numDisplays = mActivityDisplays.size();
-        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
-            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = display.getChildAt(stackNdx);
-                ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>();
-                runningTaskLists.add(stackTaskList);
-                stack.getTasksLocked(stackTaskList, callingUid, allowed);
-            }
-        }
-
-        // The lists are already sorted from most recent to oldest. Just pull the most recent off
-        // each list and add it to list. Stop when all lists are empty or maxNum reached.
-        while (maxNum > 0) {
-            long mostRecentActiveTime = Long.MIN_VALUE;
-            ArrayList<RunningTaskInfo> selectedStackList = null;
-            final int numTaskLists = runningTaskLists.size();
-            for (int stackNdx = 0; stackNdx < numTaskLists; ++stackNdx) {
-                ArrayList<RunningTaskInfo> stackTaskList = runningTaskLists.get(stackNdx);
-                if (!stackTaskList.isEmpty()) {
-                    final long lastActiveTime = stackTaskList.get(0).lastActiveTime;
-                    if (lastActiveTime > mostRecentActiveTime) {
-                        mostRecentActiveTime = lastActiveTime;
-                        selectedStackList = stackTaskList;
-                    }
-                }
-            }
-            if (selectedStackList != null) {
-                list.add(selectedStackList.remove(0));
-                --maxNum;
-            } else {
-                break;
-            }
-        }
+    @VisibleForTesting
+    void getRunningTasks(int maxNum, List<RunningTaskInfo> list,
+            @ActivityType int ignoreActivityType, @WindowingMode int ignoreWindowingMode,
+            int callingUid, boolean allowed) {
+        mRunningTasks.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode,
+                mActivityDisplays, callingUid, allowed);
     }
 
     ActivityInfo resolveActivity(Intent intent, ResolveInfo rInfo, int startFlags,
@@ -1592,7 +1565,10 @@
             return false;
         }
         if (options != null) {
-            if (options.getLaunchTaskId() != INVALID_STACK_ID) {
+            // If a launch task id is specified, then ensure that the caller is the recents
+            // component or has the START_TASKS_FROM_RECENTS permission
+            if (options.getLaunchTaskId() != INVALID_TASK_ID
+                    && !mRecentTasks.isCallerRecents(callingUid)) {
                 final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS,
                         callingPid, callingUid);
                 if (startInTaskPerm == PERMISSION_DENIED) {
@@ -2097,21 +2073,26 @@
         }
     }
 
-    void findTaskToMoveToFrontLocked(TaskRecord task, int flags, ActivityOptions options,
+    void findTaskToMoveToFront(TaskRecord task, int flags, ActivityOptions options,
             String reason, boolean forceNonResizeable) {
+        final ActivityStack currentStack = task.getStack();
+        if (currentStack == null) {
+            Slog.e(TAG, "findTaskToMoveToFront: can't move task="
+                    + task + " to front. Stack is null");
+            return;
+        }
+
         if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
             mUserLeaving = true;
         }
-        if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
-            // Caller wants the home activity moved with it.  To accomplish this,
-            // we'll just indicate that this task returns to the home task.
-            task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-        }
-        final ActivityStack currentStack = task.getStack();
-        if (currentStack == null) {
-            Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task="
-                    + task + " to front. Stack is null");
-            return;
+
+        final ActivityRecord prev = topRunningActivityLocked();
+
+        if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0
+                || (prev != null && prev.isActivityTypeRecents())) {
+            // Caller wants the home activity moved with it or the previous task is recents in which
+            // case we always return home from the task we are moving to the front.
+            moveHomeStackToFront("findTaskToMoveToFront");
         }
 
         if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
@@ -2122,7 +2103,7 @@
 
             if (stack != currentStack) {
                 task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME,
-                        "findTaskToMoveToFrontLocked");
+                        "findTaskToMoveToFront");
                 stack = currentStack;
                 // moveTaskToStackUncheckedLocked() should already placed the task on top,
                 // still need moveTaskToFrontLocked() below for any transition settings.
@@ -2161,8 +2142,8 @@
                 || mService.mSupportsFreeformWindowManagement;
     }
 
-    LaunchingTaskPositioner getLaunchingTaskPositioner() {
-        return mTaskPositioner;
+    LaunchingBoundsController getLaunchingBoundsController() {
+        return mLaunchingBoundsController;
     }
 
     protected <T extends ActivityStack> T getStack(int stackId) {
@@ -2186,89 +2167,6 @@
         return null;
     }
 
-    /**
-     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
-     * @param windowingMode The windowing mode we are checking support for.
-     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
-     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
-     * @param supportsFreeform If we should consider support for freeform multi-window.
-     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
-     * @param activityType The activity type under consideration.
-     * @return true if the windowing mode is supported.
-     */
-    boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
-            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
-            int activityType) {
-
-        if (windowingMode == WINDOWING_MODE_UNDEFINED
-                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
-            return true;
-        }
-        if (!supportsMultiWindow) {
-            return false;
-        }
-
-        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
-            return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
-                    windowingMode, activityType);
-        }
-
-        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
-            return false;
-        }
-
-        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
-            return false;
-        }
-        return true;
-    }
-
-    private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
-            @Nullable TaskRecord task, int activityType) {
-
-        // First preference if the windowing mode in the activity options if set.
-        int windowingMode = (options != null)
-                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
-        // If windowing mode is unset, then next preference is the candidate task, then the
-        // activity record.
-        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
-            if (task != null) {
-                windowingMode = task.getWindowingMode();
-            }
-            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
-                windowingMode = r.getWindowingMode();
-            }
-        }
-
-        // Make sure the windowing mode we are trying to use makes sense for what is supported.
-        boolean supportsMultiWindow = mService.mSupportsMultiWindow;
-        boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow;
-        boolean supportsFreeform = mService.mSupportsFreeformWindowManagement;
-        boolean supportsPip = mService.mSupportsPictureInPicture;
-        if (supportsMultiWindow) {
-            if (task != null) {
-                supportsMultiWindow = task.isResizeable();
-                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
-                // TODO: Do we need to check for freeform and Pip support here?
-            } else if (r != null) {
-                supportsMultiWindow = r.isResizeable();
-                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
-                supportsFreeform = r.supportsFreeform();
-                supportsPip = r.supportsPictureInPicture();
-            }
-        }
-
-        if (windowingMode != WINDOWING_MODE_UNDEFINED
-                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
-                supportsFreeform, supportsPip, activityType)) {
-            return windowingMode;
-        }
-        // Return root/systems windowing mode
-        return getWindowingMode();
-    }
-
     int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
             @Nullable TaskRecord task) {
         // Preference is given to the activity type for the activity then the task since the type
@@ -2329,7 +2227,6 @@
         }
 
         final int activityType = resolveActivityType(r, options, candidateTask);
-        int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
         T stack = null;
 
         // Next preference for stack goes to the display Id set in the activity options or the
@@ -2347,7 +2244,7 @@
             }
             final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
             if (display != null) {
-                stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+                stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
                 if (stack != null) {
                     return stack;
                 }
@@ -2365,10 +2262,14 @@
             stack = r.getStack();
         }
         if (stack != null) {
-            if (stack.isCompatible(windowingMode, activityType)) {
-                return stack;
-            }
             display = stack.getDisplay();
+            if (display != null) {
+                final int windowingMode =
+                        display.resolveWindowingMode(r, options, candidateTask, activityType);
+                if (stack.isCompatible(windowingMode, activityType)) {
+                    return stack;
+                }
+            }
         }
 
         if (display == null
@@ -2379,7 +2280,7 @@
             display = getDefaultDisplay();
         }
 
-        return display.getOrCreateStack(windowingMode, activityType, onTop);
+        return display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
     }
 
     /**
@@ -2596,6 +2497,8 @@
             final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId);
 
             if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                // Tell the display we are exiting split-screen mode.
+                toDisplay.onExitingSplitScreenMode();
                 // We are moving all tasks from the docked stack to the fullscreen stack,
                 // which is dismissing the docked stack, so resize all other stacks to
                 // fullscreen here already so we don't end up with resize trashing.
@@ -2625,35 +2528,27 @@
             final ArrayList<TaskRecord> tasks = fromStack.getAllTasks();
 
             if (!tasks.isEmpty()) {
+                mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
                 final int size = tasks.size();
-                final ActivityStack fullscreenStack = toDisplay.getOrCreateStack(
-                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, onTop);
+                for (int i = 0; i < size; ++i) {
+                    final TaskRecord task = tasks.get(i);
+                    final ActivityStack toStack = toDisplay.getOrCreateStack(
+                                null, mTmpOptions, task, task.getActivityType(), onTop);
 
-                if (onTop) {
-                    final int returnToType =
-                            toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
-                    for (int i = 0; i < size; i++) {
-                        final TaskRecord task = tasks.get(i);
+                    if (onTop) {
+                        final int returnToType =
+                                toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
                         final boolean isTopTask = i == (size - 1);
-                        if (inPinnedWindowingMode) {
-                            // Update the return-to to reflect where the pinned stack task was moved
-                            // from so that we retain the stack that was previously visible if the
-                            // pinned stack is recreated. See moveActivityToPinnedStackLocked().
-                            task.setTaskToReturnTo(returnToType);
-                        }
                         // Defer resume until all the tasks have been moved to the fullscreen stack
-                        task.reparent(fullscreenStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+                        task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
                                 isTopTask /* animate */, DEFER_RESUME,
                                 schedulePictureInPictureModeChange,
                                 "moveTasksToFullscreenStack - onTop");
-                    }
-                } else {
-                    for (int i = 0; i < size; i++) {
-                        final TaskRecord task = tasks.get(i);
+                    } else {
                         // Position the tasks in the fullscreen stack in order at the bottom of the
                         // stack. Also defer resume until all the tasks have been moved to the
                         // fullscreen stack.
-                        task.reparent(fullscreenStack, i /* position */,
+                        task.reparent(toStack, ON_TOP,
                                 REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
                                 schedulePictureInPictureModeChange,
                                 "moveTasksToFullscreenStack - NOT_onTop");
@@ -3080,23 +2975,16 @@
                     + " task=" + task);
         }
 
-        // We don't allow moving a unresizeable task to the docked stack since the docked stack is
-        // used for split-screen mode and will cause things like the docked divider to show up. We
-        // instead leave the task in its current stack or move it to the fullscreen stack if it
-        // isn't currently in a stack.
+        // Leave the task in its current stack or a fullscreen stack if it isn't resizeable and the
+        // preferred stack is in multi-window mode.
         if (inMultiWindowMode && !task.isResizeable()) {
-            Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
-                    + " Moving to stackId=" + stackId + " instead.");
-            // Temporarily disable resizeablility of the task as we don't want it to be resized if,
-            // for example, a docked stack is created which will lead to the stack we are moving
-            // from being resized and and its resizeable tasks being resized.
-            try {
-                task.mTemporarilyUnresizable = true;
-                stack = stack.getDisplay().createStack(
-                        WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
-            } finally {
-                task.mTemporarilyUnresizable = false;
+            Slog.w(TAG, "Can not move unresizeable task=" + task + " to multi-window stack=" + stack
+                    + " Moving to a fullscreen stack instead.");
+            if (prevStack != null) {
+                return prevStack;
             }
+            stack = stack.getDisplay().createStack(
+                    WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
         }
         return stack;
     }
@@ -3123,12 +3011,12 @@
         }
 
         moveActivityToPinnedStackLocked(r, null /* sourceBounds */, 0f /* aspectRatio */,
-                true /* moveHomeStackToFront */, "moveTopActivityToPinnedStack");
+                "moveTopActivityToPinnedStack");
         return true;
     }
 
     void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
-            boolean moveHomeStackToFront, String reason) {
+            String reason) {
 
         mWindowManager.deferSurfaceLayout();
 
@@ -3154,17 +3042,6 @@
                     true /* allowResizeInDockedMode */, !DEFER_RESUME);
 
             if (task.mActivities.size() == 1) {
-                // There is only one activity in the task. So, we can just move the task over to
-                // the stack without re-parenting the activity in a different task.  We don't
-                // move the home stack forward if we are currently entering picture-in-picture
-                // while pausing because that changes the focused stack and may prevent the new
-                // starting activity from resuming.
-                if (moveHomeStackToFront && task.returnsToHomeTask()
-                        && (r.state == RESUMED || !r.supportsEnterPipOnTaskSwitch)) {
-                    // Move the home stack forward if the task we just moved to the pinned stack
-                    // was launched from home so home should be visible behind it.
-                    moveHomeStackToFront(reason);
-                }
                 // Defer resume until below, and do not schedule PiP changes until we animate below
                 task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
                         false /* schedulePictureInPictureModeChange */, reason);
@@ -3799,7 +3676,7 @@
     }
 
     public void writeToProto(ProtoOutputStream proto) {
-        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
             ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
             activityDisplay.writeToProto(proto, DISPLAYS);
@@ -4287,9 +4164,9 @@
         final boolean isSecondaryDisplayPreferred =
                 (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
         final boolean inSplitScreenMode = actualStack != null
-                && actualStack.inSplitScreenWindowingMode();
+                && actualStack.getDisplay().hasSplitScreenPrimaryStack();
         if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
-                && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) {
+                && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) {
             return;
         }
 
@@ -4606,6 +4483,10 @@
                         "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 the user must confirm credentials (e.g. when first launching a work app and the
             // Work Challenge is present) let startActivityInPackage handle the intercepting.
             if (!mService.mUserController.shouldConfirmCredentials(task.userId)
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index 6f74d85..1c80282 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -26,9 +26,6 @@
 import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -151,7 +148,7 @@
     private boolean mLaunchTaskBehind;
     private int mLaunchFlags;
 
-    private Rect mLaunchBounds;
+    private Rect mLaunchBounds = new Rect();
 
     private ActivityRecord mNotTop;
     private boolean mDoResume;
@@ -169,9 +166,6 @@
     private Intent mNewTaskIntent;
     private ActivityStack mSourceStack;
     private ActivityStack mTargetStack;
-    // Indicates that we moved other task and are going to put something on top soon, so
-    // we don't want to show it redundantly or accidentally change what's shown below.
-    private boolean mMovedOtherTask;
     private boolean mMovedToFront;
     private boolean mNoAnimation;
     private boolean mKeepCurTransition;
@@ -210,7 +204,7 @@
         mLaunchFlags = 0;
         mLaunchMode = INVALID_LAUNCH_MODE;
 
-        mLaunchBounds = null;
+        mLaunchBounds.setEmpty();
 
         mNotTop = null;
         mDoResume = false;
@@ -227,7 +221,6 @@
         mSourceStack = null;
 
         mTargetStack = null;
-        mMovedOtherTask = false;
         mMovedToFront = false;
         mNoAnimation = false;
         mKeepCurTransition = false;
@@ -1184,12 +1177,8 @@
                 mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId);
         mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent,
                 mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid));
-        if (mSourceRecord != null) {
-            mStartActivity.getTask().setTaskToReturnTo(mSourceRecord);
-        }
         if (newTask) {
-            EventLog.writeEvent(
-                    EventLogTags.AM_CREATE_TASK, mStartActivity.userId,
+            EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.userId,
                     mStartActivity.getTask().taskId);
         }
         ActivityStack.logStartActivity(
@@ -1254,7 +1243,10 @@
 
         mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options);
 
-        mLaunchBounds = getOverrideBounds(r, options, inTask);
+        mLaunchBounds.setEmpty();
+
+        mSupervisor.getLaunchingBoundsController().calculateBounds(inTask, null /*layout*/, r,
+                sourceRecord, options, mLaunchBounds);
 
         mLaunchMode = r.launchMode;
 
@@ -1579,7 +1571,6 @@
                 if (mLaunchTaskBehind && mSourceRecord != null) {
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
                 }
-                mMovedOtherTask = true;
 
                 // If the launch flags carry both NEW_TASK and CLEAR_TASK, the task's activities
                 // will be cleared soon by ActivityStarter in setTaskFromIntentActivity().
@@ -1644,7 +1635,6 @@
                     intentActivity.showStartingWindow(null /* prev */, false /* newTask */,
                             true /* taskSwitch */);
                 }
-                updateTaskReturnToType(intentActivity.getTask(), mLaunchFlags, focusStack);
             }
         }
         if (!mMovedToFront && mDoResume) {
@@ -1663,27 +1653,6 @@
         return intentActivity;
     }
 
-    private void updateTaskReturnToType(
-            TaskRecord task, int launchFlags, ActivityStack focusedStack) {
-        if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
-                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
-            // Caller wants to appear on home activity.
-            task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            return;
-        } else if (focusedStack == null || focusedStack.isActivityTypeHome()) {
-            // Task will be launched over the home stack, so return home.
-            task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            return;
-        } else if (focusedStack != task.getStack() && focusedStack.isActivityTypeAssistant()) {
-            // Task was launched over the assistant stack, so return there
-            task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
-            return;
-        }
-
-        // Else we are coming from an application stack so return to an application.
-        task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-    }
-
     private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
         if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                 == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
@@ -1700,11 +1669,6 @@
             task.performClearTaskLocked();
             mReuseTask = task;
             mReuseTask.setIntent(mStartActivity);
-
-            // When we clear the task - focus will be adjusted, which will bring another task
-            // to top before we launch the activity we need. This will temporary swap their
-            // mTaskToReturnTo values and we don't want to overwrite them accidentally.
-            mMovedOtherTask = true;
         } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                 || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
             ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
@@ -1725,7 +1689,7 @@
                     // Target stack got cleared when we all activities were removed above.
                     // Go ahead and reset it.
                     mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
-                            null /* bounds */, mLaunchFlags, mOptions);
+                            mLaunchFlags, mOptions);
                     mTargetStack.addTask(task,
                             !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
                 }
@@ -1776,8 +1740,7 @@
 
     private int setTaskFromReuseOrCreateNewTask(
             TaskRecord taskToAffiliate, ActivityStack topStack) {
-        mTargetStack = computeStackFocus(
-                mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions);
+        mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
 
         // Do no move the target stack to front yet, as we might bail if
         // isLockTaskModeViolation fails below.
@@ -1806,15 +1769,6 @@
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
 
-        if (!mMovedOtherTask) {
-            // If stack id is specified in activity options, usually it means that activity is
-            // launched not from currently focused stack (e.g. from SysUI or from shell) - in
-            // that case we check the target stack.
-            // TODO: Not sure I understand the value or use of the commented out code and the
-            // comment above. See if this causes any issues and why...
-            updateTaskReturnToType(mStartActivity.getTask(), mLaunchFlags,
-                    /*preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : */topStack);
-        }
         if (mDoResume) {
             mTargetStack.moveToFront("reuseOrNewTask");
         }
@@ -1962,7 +1916,7 @@
             return START_TASK_TO_FRONT;
         }
 
-        if (mLaunchBounds != null) {
+        if (!mLaunchBounds.isEmpty()) {
             // TODO: Shouldn't we already know what stack to use by the time we get here?
             ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP);
             if (stack != mInTask.getStack()) {
@@ -1985,7 +1939,7 @@
     }
 
     void updateBounds(TaskRecord task, Rect bounds) {
-        if (bounds == null) {
+        if (bounds.isEmpty()) {
             return;
         }
 
@@ -1998,8 +1952,7 @@
     }
 
     private void setTaskToCurrentTopOrCreateNewTask() {
-        mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags,
-                mOptions);
+        mTargetStack = computeStackFocus(mStartActivity, false, mLaunchFlags, mOptions);
         if (mDoResume) {
             mTargetStack.moveToFront("addingToTopTask");
         }
@@ -2062,8 +2015,8 @@
         }
     }
 
-    private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds,
-            int launchFlags, ActivityOptions aOptions) {
+    private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, int launchFlags,
+            ActivityOptions aOptions) {
         final TaskRecord task = r.getTask();
         ActivityStack stack = getLaunchStack(r, launchFlags, task, aOptions);
         if (stack != null) {
@@ -2214,15 +2167,6 @@
         }
     }
 
-    private Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
-        Rect newBounds = null;
-        if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
-                && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
-            newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
-        }
-        return newBounds;
-    }
-
     private boolean isLaunchModeOneOf(int mode1, int mode2) {
         return mode1 == mLaunchMode || mode2 == mLaunchMode;
     }
diff --git a/com/android/server/am/AppTaskImpl.java b/com/android/server/am/AppTaskImpl.java
index 38b3039..17626ea 100644
--- a/com/android/server/am/AppTaskImpl.java
+++ b/com/android/server/am/AppTaskImpl.java
@@ -82,7 +82,7 @@
                 if (tr == null) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
-                return RecentTasks.createRecentTaskInfo(tr);
+                return mService.getRecentTasks().createRecentTaskInfo(tr);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
diff --git a/com/android/server/am/BroadcastQueue.java b/com/android/server/am/BroadcastQueue.java
index c62cc38..aa82d00 100644
--- a/com/android/server/am/BroadcastQueue.java
+++ b/com/android/server/am/BroadcastQueue.java
@@ -1198,11 +1198,6 @@
                         + " (uid " + r.callingUid + ")");
                 skip = true;
             }
-            if (!skip) {
-                r.manifestCount++;
-            } else {
-                r.manifestSkipCount++;
-            }
             if (r.curApp != null && r.curApp.crashing) {
                 // If the target process is crashing, just skip it.
                 Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
@@ -1283,6 +1278,16 @@
                 }
             }
 
+            if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
+                    && !mService.mUserController
+                    .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
+                            0 /* flags */)) {
+                skip = true;
+                Slog.w(TAG,
+                        "Skipping delivery to " + info.activityInfo.packageName + " / "
+                                + info.activityInfo.applicationInfo.uid + " : user is not running");
+            }
+
             if (skip) {
                 if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                         "Skipping delivery of ordered [" + mQueueName + "] "
@@ -1291,9 +1296,11 @@
                 r.receiver = null;
                 r.curFilter = null;
                 r.state = BroadcastRecord.IDLE;
+                r.manifestSkipCount++;
                 scheduleBroadcastsLocked();
                 return;
             }
+            r.manifestCount++;
 
             r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
             r.state = BroadcastRecord.APP_RECEIVE;
@@ -1302,7 +1309,7 @@
             if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
                 Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
                         + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
-                        + info.activityInfo.applicationInfo.uid);
+                        + receiverUid);
             }
 
             if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) {
@@ -1365,7 +1372,7 @@
                 // and mark the broadcast record as ready for the next.
                 Slog.w(TAG, "Unable to launch app "
                         + info.activityInfo.applicationInfo.packageName + "/"
-                        + info.activityInfo.applicationInfo.uid + " for broadcast "
+                        + receiverUid + " for broadcast "
                         + r.intent + ": process is bad");
                 logBroadcastReceiverDiscardLocked(r);
                 finishReceiverLocked(r, r.resultCode, r.resultData,
diff --git a/com/android/server/am/LaunchingActivityPositioner.java b/com/android/server/am/LaunchingActivityPositioner.java
new file mode 100644
index 0000000..d5f9cf3
--- /dev/null
+++ b/com/android/server/am/LaunchingActivityPositioner.java
@@ -0,0 +1,63 @@
+/*
+ * 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 android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner;
+
+/**
+ * An implementation of {@link LaunchingBoundsPositioner}, which applies the launch bounds specified
+ * inside {@link ActivityOptions#getLaunchBounds()}.
+ */
+public class LaunchingActivityPositioner implements LaunchingBoundsPositioner {
+    private final ActivityStackSupervisor mSupervisor;
+
+    LaunchingActivityPositioner(ActivityStackSupervisor activityStackSupervisor) {
+        mSupervisor = activityStackSupervisor;
+    }
+
+    @Override
+    public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+            ActivityRecord activity, ActivityRecord source,
+            ActivityOptions options, Rect current, Rect result) {
+        // We only care about figuring out bounds for activities.
+        if (activity == null) {
+            return RESULT_SKIP;
+        }
+
+        // Activity must be resizeable in the specified task.
+        if (!(mSupervisor.canUseActivityOptionsLaunchBounds(options)
+            && (activity.isResizeable() || (task != null && task.isResizeable())))) {
+            return RESULT_SKIP;
+        }
+
+        final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+
+        // Bounds weren't valid.
+        if (bounds == null) {
+            return RESULT_SKIP;
+        }
+
+        result.set(bounds);
+
+        // When this is the most explicit position specification so we should not allow further
+        // modification of the position.
+        return RESULT_DONE;
+    }
+}
diff --git a/com/android/server/am/LaunchingBoundsController.java b/com/android/server/am/LaunchingBoundsController.java
new file mode 100644
index 0000000..c762f7f
--- /dev/null
+++ b/com/android/server/am/LaunchingBoundsController.java
@@ -0,0 +1,163 @@
+/*
+ * 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 android.annotation.IntDef;
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo.WindowLayout;
+import android.graphics.Rect;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+/**
+ * {@link LaunchingBoundsController} calculates the launch bounds by coordinating between registered
+ * {@link LaunchingBoundsPositioner}.
+ */
+class LaunchingBoundsController {
+    private final List<LaunchingBoundsPositioner> mPositioners = new ArrayList<>();
+
+    // Temporary {@link Rect} for calculations. This is kept separate from {@code mTmpCurrent} and
+    // {@code mTmpResult} to prevent clobbering values.
+    private final Rect mTmpRect = new Rect();
+
+    private final Rect mTmpCurrent = new Rect();
+    private final Rect mTmpResult = new Rect();
+
+    /**
+     * Creates a {@link LaunchingBoundsController} with default registered
+     * {@link LaunchingBoundsPositioner}s.
+     */
+    void registerDefaultPositioners(ActivityStackSupervisor supervisor) {
+        // {@link LaunchingTaskPositioner} handles window layout preferences.
+        registerPositioner(new LaunchingTaskPositioner());
+
+        // {@link LaunchingActivityPositioner} is the most specific positioner and thus should be
+        // registered last (applied first) out of the defaults.
+        registerPositioner(new LaunchingActivityPositioner(supervisor));
+    }
+
+    /**
+     * Returns the position calculated by the registered positioners
+     * @param task      The {@link TaskRecord} currently being positioned.
+     * @param layout    The specified {@link WindowLayout}.
+     * @param activity  The {@link ActivityRecord} currently being positioned.
+     * @param source    The {@link ActivityRecord} from which activity was started from.
+     * @param options   The {@link ActivityOptions} specified for the activity.
+     * @param result    The resulting bounds. If no bounds are set, {@link Rect#isEmpty()} will be
+     *                  {@code true}.
+     */
+    void calculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+            ActivityRecord source, ActivityOptions options, Rect result) {
+        result.setEmpty();
+
+        // We start at the last registered {@link LaunchingBoundsPositioner} as this represents
+        // The positioner closest to the product level. Moving back through the list moves closer to
+        // the platform logic.
+        for (int i = mPositioners.size() - 1; i >= 0; --i) {
+            mTmpResult.setEmpty();
+            mTmpCurrent.set(result);
+            final LaunchingBoundsPositioner positioner = mPositioners.get(i);
+
+            switch(positioner.onCalculateBounds(task, layout, activity, source, options,
+                    mTmpCurrent, mTmpResult)) {
+                case RESULT_SKIP:
+                    // Do not apply any results when we are told to skip
+                    continue;
+                case RESULT_DONE:
+                    // Set result and return immediately.
+                    result.set(mTmpResult);
+                    return;
+                case RESULT_CONTINUE:
+                    // Set result and continue
+                    result.set(mTmpResult);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * A convenience method for laying out a task.
+     * @return {@code true} if bounds were set on the task. {@code false} otherwise.
+     */
+    boolean layoutTask(TaskRecord task, WindowLayout layout) {
+        calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/,
+                mTmpRect);
+
+        if (mTmpRect.isEmpty()) {
+            return false;
+        }
+
+        task.updateOverrideConfiguration(mTmpRect);
+
+        return true;
+    }
+
+    /**
+     * Adds a positioner to participate in future bounds calculation. Note that the last registered
+     * {@link LaunchingBoundsPositioner} will be the first to calculate the bounds.
+     */
+    void registerPositioner(LaunchingBoundsPositioner positioner) {
+        if (mPositioners.contains(positioner)) {
+            return;
+        }
+
+        mPositioners.add(positioner);
+    }
+
+    /**
+     * An interface implemented by those wanting to participate in bounds calculation.
+     */
+    interface LaunchingBoundsPositioner {
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE})
+        @interface Result {}
+
+        // Returned when the positioner does not want to influence the bounds calculation
+        int RESULT_SKIP = 0;
+        // Returned when the positioner has changed the bounds and would like its results to be the
+        // final bounds applied.
+        int RESULT_DONE = 1;
+        // Returned when the positioner has changed the bounds but is okay with other positioners
+        // influencing the bounds.
+        int RESULT_CONTINUE = 2;
+
+        /**
+         * Called when asked to calculate bounds.
+         * @param task      The {@link TaskRecord} currently being positioned.
+         * @param layout    The specified {@link WindowLayout}.
+         * @param activity  The {@link ActivityRecord} currently being positioned.
+         * @param source    The {@link ActivityRecord} activity was started from.
+         * @param options   The {@link ActivityOptions} specified for the activity.
+         * @param current   The current bounds. This can differ from the initial bounds as it
+         *                  represents the modified bounds up to this point.
+         * @param result    The {@link Rect} which the positioner should return its modified bounds.
+         *                  Any merging of the current bounds should be already applied to this
+         *                  value as well before returning.
+         * @return          A {@link Result} representing the result of the bounds calculation.
+         */
+        @Result
+        int onCalculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+                ActivityRecord source, ActivityOptions options, Rect current, Rect result);
+    }
+}
diff --git a/com/android/server/am/LaunchingTaskPositioner.java b/com/android/server/am/LaunchingTaskPositioner.java
index 0dc73e9..c958fca 100644
--- a/com/android/server/am/LaunchingTaskPositioner.java
+++ b/com/android/server/am/LaunchingTaskPositioner.java
@@ -19,7 +19,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -36,8 +36,10 @@
  * and compares corners of the task with corners of existing tasks. If some two pairs of corners are
  * sufficiently close enough, it shifts the bounds of the new task and tries again. When it exhausts
  * all possible shifts, it gives up and puts the task in the original position.
+ *
+ * Note that the only gravities of concern are the corners and the center.
  */
-class LaunchingTaskPositioner {
+class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoundsPositioner {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LaunchingTaskPositioner" : TAG_AM;
 
     // Determines how close window frames/corners have to be to call them colliding.
@@ -74,44 +76,51 @@
      * Tries to set task's bound in a way that it won't collide with any other task. By colliding
      * we mean that two tasks have left-top corner very close to each other, so one might get
      * obfuscated by the other one.
-     *
-     * @param task Task for which we want to find bounds that won't collide with other.
-     * @param tasks Existing tasks with which we don't want to collide.
-     * @param windowLayout Optional information from the client about how it would like to be sized
-     *                      and positioned.
      */
-    void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks,
-            @Nullable ActivityInfo.WindowLayout windowLayout) {
+    @Override
+    public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+            ActivityRecord activity, ActivityRecord source,
+            ActivityOptions options, Rect current, Rect result) {
+        // We can only apply positioning if we're in a freeform stack.
+        if (task == null || task.getStack() == null || !task.inFreeformWindowingMode()) {
+            return RESULT_SKIP;
+        }
+
+        final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks();
+
         updateAvailableRect(task, mAvailableRect);
 
-        if (windowLayout == null) {
-            positionCenter(task, tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
-                    getFreeformHeight(mAvailableRect));
-            return;
+        if (layout == null) {
+            positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
+                    getFreeformHeight(mAvailableRect), result);
+            return RESULT_CONTINUE;
         }
-        int width = getFinalWidth(windowLayout, mAvailableRect);
-        int height = getFinalHeight(windowLayout, mAvailableRect);
-        int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
-        int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+
+        int width = getFinalWidth(layout, mAvailableRect);
+        int height = getFinalHeight(layout, mAvailableRect);
+        int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+        int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
         if (verticalGravity == Gravity.TOP) {
             if (horizontalGravity == Gravity.RIGHT) {
-                positionTopRight(task, tasks, mAvailableRect, width, height);
+                positionTopRight(tasks, mAvailableRect, width, height, result);
             } else {
-                positionTopLeft(task, tasks, mAvailableRect, width, height);
+                positionTopLeft(tasks, mAvailableRect, width, height, result);
             }
         } else if (verticalGravity == Gravity.BOTTOM) {
             if (horizontalGravity == Gravity.RIGHT) {
-                positionBottomRight(task, tasks, mAvailableRect, width, height);
+                positionBottomRight(tasks, mAvailableRect, width, height, result);
             } else {
-                positionBottomLeft(task, tasks, mAvailableRect, width, height);
+                positionBottomLeft(tasks, mAvailableRect, width, height, result);
             }
         } else {
             // Some fancy gravity setting that we don't support yet. We just put the activity in the
             // center.
-            Slog.w(TAG, "Received unsupported gravity: " + windowLayout.gravity
+            Slog.w(TAG, "Received unsupported gravity: " + layout.gravity
                     + ", positioning in the center instead.");
-            positionCenter(task, tasks, mAvailableRect, width, height);
+            positionCenter(tasks, mAvailableRect, width, height, result);
         }
+
+        return RESULT_CONTINUE;
     }
 
     private void updateAvailableRect(TaskRecord task, Rect availableRect) {
@@ -179,50 +188,50 @@
         return height;
     }
 
-    private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         mTmpProposal.set(availableRect.left, availableRect.bottom - height,
                 availableRect.left + width, availableRect.bottom);
-        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
-                SHIFT_POLICY_HORIZONTAL_RIGHT);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
+                result);
     }
 
-    private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         mTmpProposal.set(availableRect.right - width, availableRect.bottom - height,
                 availableRect.right, availableRect.bottom);
-        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
-                SHIFT_POLICY_HORIZONTAL_LEFT);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
+                result);
     }
 
-    private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         mTmpProposal.set(availableRect.left, availableRect.top,
                 availableRect.left + width, availableRect.top + height);
-        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
-                SHIFT_POLICY_HORIZONTAL_RIGHT);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
+                result);
     }
 
-    private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         mTmpProposal.set(availableRect.right - width, availableRect.top,
                 availableRect.right, availableRect.top + height);
-        position(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART,
-                SHIFT_POLICY_HORIZONTAL_LEFT);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
+                result);
     }
 
-    private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks,
-            Rect availableRect, int width, int height) {
+    private void positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
         final int defaultFreeformLeft = getFreeformStartLeft(availableRect);
         final int defaultFreeformTop = getFreeformStartTop(availableRect);
         mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop,
                 defaultFreeformLeft + width, defaultFreeformTop + height);
-        position(task, tasks, availableRect, mTmpProposal, ALLOW_RESTART,
-                SHIFT_POLICY_DIAGONAL_DOWN);
+        position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN,
+                result);
     }
 
-    private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect availableRect,
-            Rect proposal, boolean allowRestart, int shiftPolicy) {
+    private void position(ArrayList<TaskRecord> tasks, Rect availableRect,
+            Rect proposal, boolean allowRestart, int shiftPolicy, Rect result) {
         mTmpOriginal.set(proposal);
         boolean restarted = false;
         while (boundsConflict(proposal, tasks)) {
@@ -252,7 +261,7 @@
                 break;
             }
         }
-        task.updateOverrideConfiguration(proposal);
+        result.set(proposal);
     }
 
     private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) {
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index 1c094c1..4b2a084 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -19,17 +19,11 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.StatusBarManager.DISABLE_BACK;
-import static android.app.StatusBarManager.DISABLE_HOME;
-import static android.app.StatusBarManager.DISABLE_MASK;
-import static android.app.StatusBarManager.DISABLE_NONE;
-import static android.app.StatusBarManager.DISABLE_RECENT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Context.DEVICE_POLICY_SERVICE;
 import static android.content.Context.STATUS_BAR_SERVICE;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_CURRENT;
-import static android.provider.Settings.Secure.LOCK_TO_APP_EXIT_LOCKED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
@@ -46,7 +40,10 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.IDevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Binder;
 import android.os.Debug;
@@ -55,6 +52,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -84,13 +82,39 @@
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
 
     @VisibleForTesting
-    static final int STATUS_BAR_MASK_LOCKED = DISABLE_MASK
-            & (~DISABLE_BACK);
+    static final int STATUS_BAR_MASK_LOCKED = StatusBarManager.DISABLE_MASK
+            & (~StatusBarManager.DISABLE_EXPAND)
+            & (~StatusBarManager.DISABLE_NOTIFICATION_TICKER)
+            & (~StatusBarManager.DISABLE_SYSTEM_INFO)
+            & (~StatusBarManager.DISABLE_BACK);
     @VisibleForTesting
-    static final int STATUS_BAR_MASK_PINNED = DISABLE_MASK
-            & (~DISABLE_BACK)
-            & (~DISABLE_HOME)
-            & (~DISABLE_RECENT);
+    static final int STATUS_BAR_MASK_PINNED = StatusBarManager.DISABLE_MASK
+            & (~StatusBarManager.DISABLE_BACK)
+            & (~StatusBarManager.DISABLE_HOME)
+            & (~StatusBarManager.DISABLE_RECENT);
+
+    private static final SparseArray<Pair<Integer, Integer>> STATUS_BAR_FLAG_MAP_LOCKED;
+    static {
+        STATUS_BAR_FLAG_MAP_LOCKED = new SparseArray<>();
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO,
+                new Pair<>(StatusBarManager.DISABLE_CLOCK, StatusBarManager.DISABLE2_SYSTEM_ICONS));
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS,
+                new Pair<>(StatusBarManager.DISABLE_NOTIFICATION_ICONS
+                        | StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
+                        StatusBarManager.DISABLE2_NOTIFICATION_SHADE));
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_HOME,
+                new Pair<>(StatusBarManager.DISABLE_HOME, StatusBarManager.DISABLE2_NONE));
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS,
+                new Pair<>(StatusBarManager.DISABLE_RECENT, StatusBarManager.DISABLE2_NONE));
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+                new Pair<>(StatusBarManager.DISABLE_NONE,
+                        StatusBarManager.DISABLE2_GLOBAL_ACTIONS));
+    }
 
     /** Tag used for disabling of keyguard */
     private static final String LOCK_TASK_TAG = "Lock-to-App";
@@ -131,6 +155,11 @@
     private final SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
 
     /**
+     * Features that are allowed by DPC to show during LockTask mode.
+     */
+    private final SparseArray<Integer> mLockTaskFeatures = new SparseArray<>();
+
+    /**
      * Store the current lock task mode. Possible values:
      * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
      * {@link ActivityManager#LOCK_TASK_MODE_PINNED}
@@ -319,30 +348,17 @@
     private void performStopLockTask(int userId) {
         // When lock task ends, we enable the status bars.
         try {
-            if (getStatusBarService() != null) {
-                getStatusBarService().disable(DISABLE_NONE, mToken,
-                        mContext.getPackageName());
+            setStatusBarState(LOCK_TASK_MODE_NONE, userId);
+            setKeyguardState(LOCK_TASK_MODE_NONE, userId);
+            if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                lockKeyguardIfNeeded();
             }
-            mWindowManager.reenableKeyguard(mToken);
             if (getDevicePolicyManager() != null) {
                 getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
             }
             if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
                 getLockTaskNotify().showPinningExitToast();
             }
-            try {
-                boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
-                        mContext.getContentResolver(),
-                        LOCK_TO_APP_EXIT_LOCKED,
-                        USER_CURRENT) != 0;
-                if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) {
-                    mWindowManager.lockNow(null);
-                    mWindowManager.dismissKeyguard(null /* callback */);
-                    getLockPatternUtils().requireCredentialEntry(USER_ALL);
-                }
-            } catch (Settings.SettingNotFoundException e) {
-                // No setting, don't lock.
-            }
         } catch (RemoteException ex) {
             throw new RuntimeException(ex);
         } finally {
@@ -430,7 +446,7 @@
         }
 
         if (andResume) {
-            mSupervisor.findTaskToMoveToFrontLocked(task, 0, null, reason,
+            mSupervisor.findTaskToMoveToFront(task, 0, null, reason,
                     lockTaskModeState != LOCK_TASK_MODE_NONE);
             mSupervisor.resumeFocusedStackTopActivityLocked();
             mWindowManager.executeAppTransition();
@@ -448,16 +464,8 @@
                 getLockTaskNotify().showPinningStartToast();
             }
             mLockTaskModeState = lockTaskModeState;
-            if (getStatusBarService() != null) {
-                int flags = 0;
-                if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
-                    flags = STATUS_BAR_MASK_LOCKED;
-                } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
-                    flags = STATUS_BAR_MASK_PINNED;
-                }
-                getStatusBarService().disable(flags, mToken, mContext.getPackageName());
-            }
-            mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+            setStatusBarState(lockTaskModeState, userId);
+            setKeyguardState(lockTaskModeState, userId);
             if (getDevicePolicyManager() != null) {
                 getDevicePolicyManager().notifyLockTaskModeChanged(true, packageName, userId);
             }
@@ -536,6 +544,135 @@
     }
 
     /**
+     * Update the UI features that are enabled for LockTask mode.
+     * @param userId Which user these feature flags are associated with
+     * @param flags Bitfield of feature flags
+     * @see DevicePolicyManager#setLockTaskFeatures(ComponentName, int)
+     */
+    void updateLockTaskFeatures(int userId, int flags) {
+        int oldFlags = getLockTaskFeaturesForUser(userId);
+        if (flags == oldFlags) {
+            return;
+        }
+
+        mLockTaskFeatures.put(userId, flags);
+        TaskRecord lockedTask = getLockedTask();
+        if (lockedTask != null && userId == lockedTask.userId) {
+            mHandler.post(() -> {
+                if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+                    setStatusBarState(mLockTaskModeState, userId);
+                    setKeyguardState(mLockTaskModeState, userId);
+                }
+            });
+        }
+    }
+
+    /**
+     * Helper method for configuring the status bar disabled state.
+     * Should only be called on the handler thread to avoid race.
+     */
+    private void setStatusBarState(int lockTaskModeState, int userId) {
+        IStatusBarService statusBar = getStatusBarService();
+        if (statusBar == null) {
+            Slog.e(TAG, "Can't find StatusBarService");
+            return;
+        }
+
+        // Default state, when lockTaskModeState == LOCK_TASK_MODE_NONE
+        int flags1 = StatusBarManager.DISABLE_NONE;
+        int flags2 = StatusBarManager.DISABLE2_NONE;
+
+        if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
+            flags1 = STATUS_BAR_MASK_PINNED;
+
+        } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+            int lockTaskFeatures = getLockTaskFeaturesForUser(userId);
+            Pair<Integer, Integer> statusBarFlags = getStatusBarDisableFlags(lockTaskFeatures);
+            flags1 = statusBarFlags.first;
+            flags2 = statusBarFlags.second;
+        }
+
+        try {
+            statusBar.disable(flags1, mToken, mContext.getPackageName());
+            statusBar.disable2(flags2, mToken, mContext.getPackageName());
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to set status bar flags", e);
+        }
+    }
+
+    /**
+     * Helper method for configuring the keyguard disabled state.
+     * Should only be called on the handler thread to avoid race.
+     */
+    private void setKeyguardState(int lockTaskModeState, int userId) {
+        if (lockTaskModeState == LOCK_TASK_MODE_NONE) {
+            mWindowManager.reenableKeyguard(mToken);
+
+        } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+            int lockTaskFeatures = getLockTaskFeaturesForUser(userId);
+            if ((DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD & lockTaskFeatures) == 0) {
+                mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+            } else {
+                mWindowManager.reenableKeyguard(mToken);
+            }
+
+        } else { // lockTaskModeState == LOCK_TASK_MODE_PINNED
+            mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+        }
+    }
+
+    /**
+     * Helper method for locking the device immediately. This may be necessary when the device
+     * leaves the pinned mode.
+     */
+    private void lockKeyguardIfNeeded() {
+        try {
+            boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
+                    USER_CURRENT) != 0;
+            if (shouldLockKeyguard) {
+                mWindowManager.lockNow(null);
+                mWindowManager.dismissKeyguard(null /* callback */);
+                getLockPatternUtils().requireCredentialEntry(USER_ALL);
+            }
+        } catch (Settings.SettingNotFoundException e) {
+            // No setting, don't lock.
+        }
+    }
+
+    /**
+     * Translates from LockTask feature flags to StatusBarManager disable and disable2 flags.
+     * @param lockTaskFlags Bitfield of flags as per
+     *                      {@link DevicePolicyManager#setLockTaskFeatures(ComponentName, int)}
+     * @return A {@link Pair} of {@link StatusBarManager#disable(int)} and
+     *         {@link StatusBarManager#disable2(int)} flags
+     */
+    @VisibleForTesting
+    Pair<Integer, Integer> getStatusBarDisableFlags(int lockTaskFlags) {
+        // Everything is disabled by default
+        int flags1 = StatusBarManager.DISABLE_MASK;
+        int flags2 = StatusBarManager.DISABLE2_MASK;
+        for (int i = STATUS_BAR_FLAG_MAP_LOCKED.size() - 1; i >= 0; i--) {
+            Pair<Integer, Integer> statusBarFlags = STATUS_BAR_FLAG_MAP_LOCKED.valueAt(i);
+            if ((STATUS_BAR_FLAG_MAP_LOCKED.keyAt(i) & lockTaskFlags) != 0) {
+                flags1 &= ~statusBarFlags.first;
+                flags2 &= ~statusBarFlags.second;
+            }
+        }
+        // Some flags are not used for LockTask purposes, so we mask them
+        flags1 &= STATUS_BAR_MASK_LOCKED;
+        return new Pair<>(flags1, flags2);
+    }
+
+    /**
+     * Gets the cached value of LockTask feature flags for a specific user.
+     */
+    private int getLockTaskFeaturesForUser(int userId) {
+        return mLockTaskFeatures.get(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+    }
+
+    /**
      * @return the topmost locked task
      */
     private TaskRecord getLockedTask() {
diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java
index ed3f503..0b9e0a2 100644
--- a/com/android/server/am/RecentTasks.java
+++ b/com/android/server/am/RecentTasks.java
@@ -37,8 +37,6 @@
 import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 
-import com.google.android.collect.Sets;
-
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.content.ComponentName;
@@ -58,15 +56,16 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArraySet;
-import android.util.MutableBoolean;
-import android.util.MutableInt;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.am.TaskRecord.TaskActivitiesReport;
+
+import com.google.android.collect.Sets;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -75,7 +74,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
@@ -126,6 +124,13 @@
     private final UserController mUserController;
 
     /**
+     * Keeps track of the static recents package/component which is granted additional permissions
+     * to call recents-related APIs.
+     */
+    private int mRecentsUid = -1;
+    private ComponentName mRecentsComponent = null;
+
+    /**
      * Mapping of user id -> whether recent tasks have been loaded for that user.
      */
     private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(
@@ -154,6 +159,7 @@
     private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>();
     private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>();
     private final SparseBooleanArray mTmpQuietProfileUserIds = new SparseBooleanArray();
+    private final TaskActivitiesReport mTmpReport = new TaskActivitiesReport();
 
     @VisibleForTesting
     RecentTasks(ActivityManagerService service, TaskPersister taskPersister,
@@ -173,7 +179,7 @@
         mTaskPersister = new TaskPersister(systemDir, stackSupervisor, service, this);
         mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic();
         mHasVisibleRecentTasks = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
-        loadParametersFromResources(service.mContext.getResources());
+        loadParametersFromResources(res);
     }
 
     @VisibleForTesting
@@ -217,6 +223,47 @@
                 : -1;
     }
 
+    /**
+     * Loads the static recents component.  This is called after the system is ready, but before
+     * any dependent services (like SystemUI) is started.
+     */
+    void loadRecentsComponent(Resources res) {
+        final String rawRecentsComponent = res.getString(
+                com.android.internal.R.string.config_recentsComponentName);
+        if (TextUtils.isEmpty(rawRecentsComponent)) {
+            return;
+        }
+
+        final ComponentName cn = ComponentName.unflattenFromString(rawRecentsComponent);
+        if (cn != null) {
+            try {
+                final ApplicationInfo appInfo = AppGlobals.getPackageManager()
+                        .getApplicationInfo(cn.getPackageName(), 0, mService.mContext.getUserId());
+                if (appInfo != null) {
+                    mRecentsUid = appInfo.uid;
+                    mRecentsComponent = cn;
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Could not load application info for recents component: " + cn);
+            }
+        }
+    }
+
+    /**
+     * @return whether the current caller has the same uid as the recents component.
+     */
+    boolean isCallerRecents(int callingUid) {
+        return UserHandle.isSameApp(callingUid, mRecentsUid);
+    }
+
+    /**
+     * @return whether the given component is the recents component and shares the same uid as the
+     *         recents component.
+     */
+    boolean isRecentsComponent(ComponentName cn, int uid) {
+        return cn.equals(mRecentsComponent) && UserHandle.isSameApp(uid, mRecentsUid);
+    }
+
     void registerCallback(Callbacks callback) {
         mCallbacks.add(callback);
     }
@@ -339,6 +386,7 @@
     }
 
     void onSystemReadyLocked() {
+        loadRecentsComponent(mService.mContext.getResources());
         mTasks.clear();
         mTaskPersister.startPersisting();
     }
@@ -690,7 +738,7 @@
                     continue;
                 }
 
-                ActivityManager.RecentTaskInfo rti = RecentTasks.createRecentTaskInfo(tr);
+                final ActivityManager.RecentTaskInfo rti = createRecentTaskInfo(tr);
                 if (!getDetailedTasks) {
                     rti.baseIntent.replaceExtras((Bundle)null);
                 }
@@ -1327,12 +1375,14 @@
 
     void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) {
         pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)");
+        pw.println("mRecentsUid=" + mRecentsUid);
+        pw.println("mRecentsComponent=" + mRecentsComponent);
         if (mTasks.isEmpty()) {
             return;
         }
 
-        final MutableBoolean printedAnything = new MutableBoolean(false);
-        final MutableBoolean printedHeader = new MutableBoolean(false);
+        boolean printedAnything = false;
+        boolean printedHeader = false;
         final int size = mTasks.size();
         for (int i = 0; i < size; i++) {
             final TaskRecord tr = mTasks.get(i);
@@ -1341,10 +1391,10 @@
                 continue;
             }
 
-            if (!printedHeader.value) {
+            if (!printedHeader) {
                 pw.println("  Recent tasks:");
-                printedHeader.value = true;
-                printedAnything.value = true;
+                printedHeader = true;
+                printedAnything = true;
             }
             pw.print("  * Recent #"); pw.print(i); pw.print(": ");
             pw.println(tr);
@@ -1353,7 +1403,7 @@
             }
         }
 
-        if (!printedAnything.value) {
+        if (!printedAnything) {
             pw.println("  (nothing)");
         }
     }
@@ -1361,7 +1411,7 @@
     /**
      * Creates a new RecentTaskInfo from a TaskRecord.
      */
-    static ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) {
+    ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) {
         // Update the task description to reflect any changes in the task stack
         tr.updateTaskDescription();
 
@@ -1387,24 +1437,10 @@
         rti.resizeMode = tr.mResizeMode;
         rti.configuration.setTo(tr.getConfiguration());
 
-        ActivityRecord base = null;
-        ActivityRecord top = null;
-        ActivityRecord tmp;
-
-        for (int i = tr.mActivities.size() - 1; i >= 0; --i) {
-            tmp = tr.mActivities.get(i);
-            if (tmp.finishing) {
-                continue;
-            }
-            base = tmp;
-            if (top == null || (top.state == ActivityState.INITIALIZING)) {
-                top = base;
-            }
-            rti.numActivities++;
-        }
-
-        rti.baseActivity = (base != null) ? base.intent.getComponent() : null;
-        rti.topActivity = (top != null) ? top.intent.getComponent() : null;
+        tr.getNumRunningActivities(mTmpReport);
+        rti.numActivities = mTmpReport.numActivities;
+        rti.baseActivity = (mTmpReport.base != null) ? mTmpReport.base.intent.getComponent() : null;
+        rti.topActivity = (mTmpReport.top != null) ? mTmpReport.top.intent.getComponent() : null;
 
         return rti;
     }
diff --git a/com/android/server/am/RunningTasks.java b/com/android/server/am/RunningTasks.java
new file mode 100644
index 0000000..400b03a
--- /dev/null
+++ b/com/android/server/am/RunningTasks.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.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
+
+/**
+ * Class for resolving the set of running tasks in the system.
+ */
+class RunningTasks {
+
+    // Comparator to sort by last active time (descending)
+    private static final Comparator<TaskRecord> LAST_ACTIVE_TIME_COMPARATOR =
+            (o1, o2) -> Long.signum(o2.lastActiveTime - o1.lastActiveTime);
+
+    private final TaskRecord.TaskActivitiesReport mTmpReport =
+            new TaskRecord.TaskActivitiesReport();
+    private final TreeSet<TaskRecord> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
+    private final ArrayList<TaskRecord> mTmpStackTasks = new ArrayList<>();
+
+    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
+
+        // Gather all of the tasks across all of the tasks, and add them to the sorted set
+        mTmpSortedSet.clear();
+        mTmpStackTasks.clear();
+        final int numDisplays = activityDisplays.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final ActivityDisplay display = activityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.getRunningTasks(mTmpStackTasks, ignoreActivityType, ignoreWindowingMode,
+                        callingUid, allowed);
+                for (int i = mTmpStackTasks.size() - 1; i >= 0; i--) {
+                    mTmpSortedSet.addAll(mTmpStackTasks);
+                }
+            }
+        }
+
+        // Take the first {@param maxNum} tasks and create running task infos for them
+        final Iterator<TaskRecord> iter = mTmpSortedSet.iterator();
+        while (iter.hasNext()) {
+            if (maxNum == 0) {
+                break;
+            }
+
+            final TaskRecord task = iter.next();
+            list.add(createRunningTaskInfo(task));
+            maxNum--;
+        }
+    }
+
+    /**
+     * Constructs a {@link RunningTaskInfo} from a given {@param task}.
+     */
+    private RunningTaskInfo createRunningTaskInfo(TaskRecord task) {
+        task.getNumRunningActivities(mTmpReport);
+
+        final RunningTaskInfo ci = new RunningTaskInfo();
+        ci.id = task.taskId;
+        ci.stackId = task.getStackId();
+        ci.baseActivity = mTmpReport.base.intent.getComponent();
+        ci.topActivity = mTmpReport.top.intent.getComponent();
+        ci.lastActiveTime = task.lastActiveTime;
+        ci.description = task.lastDescription;
+        ci.numActivities = mTmpReport.numActivities;
+        ci.numRunning = mTmpReport.numRunning;
+        ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode();
+        ci.resizeMode = task.mResizeMode;
+        ci.configuration.setTo(task.getConfiguration());
+        return ci;
+    }
+}
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index c451235..1b5a1ce 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -30,7 +30,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
@@ -74,7 +76,6 @@
 import static com.android.server.am.proto.TaskRecordProto.ORIG_ACTIVITY;
 import static com.android.server.am.proto.TaskRecordProto.REAL_ACTIVITY;
 import static com.android.server.am.proto.TaskRecordProto.RESIZE_MODE;
-import static com.android.server.am.proto.TaskRecordProto.RETURN_TO_TYPE;
 import static com.android.server.am.proto.TaskRecordProto.STACK_ID;
 import static com.android.server.am.proto.TaskRecordProto.ACTIVITY_TYPE;
 
@@ -111,6 +112,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.util.XmlUtils;
+import com.android.server.am.ActivityStack.ActivityState;
 import com.android.server.wm.AppWindowContainerController;
 import com.android.server.wm.ConfigurationContainer;
 import com.android.server.wm.StackWindowController;
@@ -172,7 +174,6 @@
     // Current version of the task record we persist. Used to check if we need to run any upgrade
     // code.
     private static final int PERSIST_TASK_VERSION = 1;
-    private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
 
     static final int INVALID_TASK_ID = -1;
     private static final int INVALID_MIN_SIZE = -1;
@@ -187,13 +188,13 @@
             REPARENT_KEEP_STACK_AT_FRONT,
             REPARENT_LEAVE_STACK_IN_PLACE
     })
-    public @interface ReparentMoveStackMode {}
+    @interface ReparentMoveStackMode {}
     // Moves the stack to the front if it was not at the front
-    public static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
+    static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
     // Only moves the stack to the front if it was focused or front most already
-    public static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
+    static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
     // Do not move the stack as a part of reparenting
-    public static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+    static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
 
     final int taskId;       // Unique identifier for this task.
     String affinity;        // The affinity name for this task, or null; may change identity.
@@ -231,9 +232,6 @@
     private boolean mSupportsPictureInPicture;  // Whether or not this task and its activities
             // support PiP. Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag
             // of the root activity.
-    boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
-                                     // changes on a temporary basis.
-
     /** Can't be put in lockTask mode. */
     final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
     /** Can enter app pinning with user approval. Can never start over existing lockTask task. */
@@ -268,12 +266,6 @@
      * (positive) or back (negative). Absolute value indicates time. */
     long mLastTimeMoved = System.currentTimeMillis();
 
-    /** Indication of what to run next when task exits. */
-    // TODO: Shouldn't be needed if we have things in visual order. I.e. we stop using stacks or
-    // have a stack per standard application type...
-    /*@WindowConfiguration.ActivityType*/
-    private int mTaskToReturnTo = ACTIVITY_TYPE_STANDARD;
-
     /** If original intent did not allow relinquishing task identity, save that information */
     private boolean mNeverRelinquishIdentity = true;
 
@@ -281,7 +273,6 @@
     // do not want to delete the stack when the task goes empty.
     private boolean mReuseTask = false;
 
-    private final String mFilename;
     CharSequence lastDescription; // Last description captured for this item.
 
     int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
@@ -327,8 +318,6 @@
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
-                TaskPersister.IMAGE_EXTENSION;
         userId = UserHandle.getUserId(info.applicationInfo.uid);
         taskId = _taskId;
         lastActiveTime = SystemClock.elapsedRealtime();
@@ -348,8 +337,6 @@
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             TaskDescription _taskDescription) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
-                TaskPersister.IMAGE_EXTENSION;
         userId = UserHandle.getUserId(info.applicationInfo.uid);
         taskId = _taskId;
         lastActiveTime = SystemClock.elapsedRealtime();
@@ -368,7 +355,6 @@
         maxRecents = Math.min(Math.max(info.maxRecents, 1),
                 ActivityManager.getMaxAppRecentsLimitStatic());
 
-        mTaskToReturnTo = ACTIVITY_TYPE_HOME;
         lastTaskDescription = _taskDescription;
         touchActiveTime();
         mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity);
@@ -385,8 +371,6 @@
             int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended,
             boolean userSetupComplete, int minWidth, int minHeight) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
-                TaskPersister.IMAGE_EXTENSION;
         taskId = _taskId;
         intent = _intent;
         affinityIntent = _affinityIntent;
@@ -401,7 +385,6 @@
         isAvailable = true;
         autoRemoveRecents = _autoRemoveRecents;
         askedCompatMode = _askedCompatMode;
-        mTaskToReturnTo = ACTIVITY_TYPE_HOME;
         userId = _userId;
         mUserSetupComplete = userSetupComplete;
         effectiveUid = _effectiveUid;
@@ -707,7 +690,7 @@
             } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
                 Rect bounds = getLaunchBounds();
                 if (bounds == null) {
-                    toStack.layoutTaskInStack(this, null);
+                    mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
                     bounds = mBounds;
                 }
                 kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
@@ -904,29 +887,9 @@
         return this.intent.filterEquals(intent);
     }
 
-    void setTaskToReturnTo(/*@WindowConfiguration.ActivityType*/ int taskToReturnTo) {
-        mTaskToReturnTo = taskToReturnTo == ACTIVITY_TYPE_RECENTS
-                ? ACTIVITY_TYPE_HOME : taskToReturnTo;
-    }
-
-    void setTaskToReturnTo(ActivityRecord source) {
-        if (source.isActivityTypeRecents()) {
-            setTaskToReturnTo(ACTIVITY_TYPE_RECENTS);
-        } else if (source.isActivityTypeAssistant()) {
-            setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
-        }
-    }
-
-    int getTaskToReturnTo() {
-        return mTaskToReturnTo;
-    }
-
-    boolean returnsToHomeTask() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
-    }
-
-    boolean returnsToStandardTask() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_STANDARD;
+    boolean returnsToHomeStack() {
+        final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
+        return (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
     }
 
     void setPrevAffiliate(TaskRecord prevAffiliate) {
@@ -1098,6 +1061,36 @@
         return null;
     }
 
+    /**
+     * Return the number of running activities, and the number of non-finishing/initializing
+     * activities in the provided {@param reportOut} respectively.
+     */
+    void getNumRunningActivities(TaskActivitiesReport reportOut) {
+        reportOut.reset();
+        for (int i = mActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord r = mActivities.get(i);
+            if (r.finishing) {
+                continue;
+            }
+
+            reportOut.base = r;
+
+            // Increment the total number of non-finishing activities
+            reportOut.numActivities++;
+
+            if (reportOut.top == null || (reportOut.top.state == ActivityState.INITIALIZING)) {
+                reportOut.top = r;
+                // Reset the number of running activities until we hit the first non-initializing
+                // activity
+                reportOut.numRunning = 0;
+            }
+            if (r.app != null && r.app.thread != null) {
+                // Increment the number of actually running activities
+                reportOut.numRunning++;
+            }
+        }
+    }
+
     boolean okToShowLocked() {
         // NOTE: If {@link TaskRecord#topRunningActivityLocked} return is not null then it is
         // okay to show the activity when locked.
@@ -1447,17 +1440,9 @@
                 " mLockTaskAuth=" + lockTaskAuthToString());
     }
 
-    boolean isOverHomeStack() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
-    }
-
-    boolean isOverAssistantStack() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_ASSISTANT;
-    }
-
     private boolean isResizeable(boolean checkSupportsPip) {
         return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
-                || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable;
+                || (checkSupportsPip && mSupportsPictureInPicture));
     }
 
     boolean isResizeable() {
@@ -2089,7 +2074,7 @@
             if (mLastNonFullscreenBounds != null) {
                 updateOverrideConfiguration(mLastNonFullscreenBounds);
             } else {
-                inStack.layoutTaskInStack(this, null);
+                mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
             }
         } else {
             updateOverrideConfiguration(inStack.mBounds);
@@ -2164,13 +2149,11 @@
             pw.print(prefix); pw.print("realActivity=");
             pw.println(realActivity.flattenToShortString());
         }
-        if (autoRemoveRecents || isPersistable || !isActivityTypeStandard()
-                || mTaskToReturnTo != ACTIVITY_TYPE_STANDARD || numFullscreen != 0) {
+        if (autoRemoveRecents || isPersistable || !isActivityTypeStandard() || numFullscreen != 0) {
             pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents);
                     pw.print(" isPersistable="); pw.print(isPersistable);
                     pw.print(" numFullscreen="); pw.print(numFullscreen);
-                    pw.print(" activityType="); pw.print(getActivityType());
-                    pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
+                    pw.print(" activityType="); pw.println(getActivityType());
         }
         if (rootWasReset || mNeverRelinquishIdentity || mReuseTask
                 || mLockTaskAuth != LOCK_TASK_AUTH_PINNABLE) {
@@ -2253,7 +2236,7 @@
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         proto.write(ID, taskId);
         for (int i = mActivities.size() - 1; i >= 0; i--) {
             ActivityRecord activity = mActivities.get(i);
@@ -2270,7 +2253,6 @@
             proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString());
         }
         proto.write(ACTIVITY_TYPE, getActivityType());
-        proto.write(RETURN_TO_TYPE, mTaskToReturnTo);
         proto.write(RESIZE_MODE, mResizeMode);
         proto.write(FULLSCREEN, mFullscreen);
         if (mBounds != null) {
@@ -2280,4 +2262,19 @@
         proto.write(MIN_HEIGHT, mMinHeight);
         proto.end(token);
     }
+
+    /**
+     * See {@link #getNumRunningActivities(TaskActivitiesReport)}.
+     */
+    static class TaskActivitiesReport {
+        int numRunning;
+        int numActivities;
+        ActivityRecord top;
+        ActivityRecord base;
+
+        void reset() {
+            numRunning = numActivities = 0;
+            top = base = null;
+        }
+    }
 }
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index 4aa8adb..44f83b0 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -117,20 +117,6 @@
 class UserController implements Handler.Callback {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
 
-    /**
-     * Maximum number of users we allow to be running at a time, including the system user and
-     * its profiles.
-     * Note changing this to 2 is not recommended, since that would mean, if the user uses
-     * work profile and then switch to a secondary user, then the work profile user would be killed,
-     * which should work fine, but aggressively killing the work profile user that has just been
-     * running could cause data loss.  (Even without work profile, witching from secondary user A
-     * to secondary user B would cause similar issues on user B.)
-     *
-     * TODO: Consider adding or replacing with "MAX_RUNNING_*SECONDARY*_USERS", which is the max
-     * number of running *secondary, switchable* users.
-     */
-    static final int MAX_RUNNING_USERS = 3;
-
     // Amount of time we wait for observers to handle a user switch before
     // giving up on them and unfreezing the screen.
     static final int USER_SWITCH_TIMEOUT_MS = 3 * 1000;
@@ -157,6 +143,17 @@
     // when it never calls back.
     private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
 
+    /**
+     * Maximum number of users we allow to be running at a time, including system user.
+     *
+     * <p>This parameter only affects how many background users will be stopped when switching to a
+     * new user. It has no impact on {@link #startUser(int, boolean)} behavior.
+     *
+     * <p>Note: Current and system user (and their related profiles) are never stopped when
+     * switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
+     */
+    int mMaxRunningUsers;
+
     // Lock for internal state.
     private final Object mLock = new Object();
 
@@ -245,26 +242,26 @@
         finishUserBoot(uss);
         startProfiles();
         synchronized (mLock) {
-            stopRunningUsersLU(MAX_RUNNING_USERS);
+            stopRunningUsersLU(mMaxRunningUsers);
         }
     }
 
     void stopRunningUsersLU(int maxRunningUsers) {
-        int num = mUserLru.size();
+        int currentlyRunning = mUserLru.size();
         int i = 0;
-        while (num > maxRunningUsers && i < mUserLru.size()) {
+        while (currentlyRunning > maxRunningUsers && i < mUserLru.size()) {
             Integer oldUserId = mUserLru.get(i);
             UserState oldUss = mStartedUsers.get(oldUserId);
             if (oldUss == null) {
                 // Shouldn't happen, but be sane if it does.
                 mUserLru.remove(i);
-                num--;
+                currentlyRunning--;
                 continue;
             }
             if (oldUss.state == UserState.STATE_STOPPING
                     || oldUss.state == UserState.STATE_SHUTDOWN) {
                 // This user is already stopping, doesn't count.
-                num--;
+                currentlyRunning--;
                 i++;
                 continue;
             }
@@ -272,16 +269,15 @@
                 // Owner/System user and current user can't be stopped. We count it as running
                 // when it is not a pure system user.
                 if (UserInfo.isSystemOnly(oldUserId)) {
-                    num--;
+                    currentlyRunning--;
                 }
                 i++;
                 continue;
             }
             // This is a user to be stopped.
-            if (stopUsersLU(oldUserId, false, null) != USER_OP_SUCCESS) {
-                num--;
+            if (stopUsersLU(oldUserId, false, null) == USER_OP_SUCCESS) {
+                currentlyRunning--;
             }
-            num--;
             i++;
         }
     }
@@ -814,7 +810,7 @@
         }
         final int profilesToStartSize = profilesToStart.size();
         int i = 0;
-        for (; i < profilesToStartSize && i < (MAX_RUNNING_USERS - 1); ++i) {
+        for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
             startUser(profilesToStart.get(i).id, /* foreground= */ false);
         }
         if (i < profilesToStartSize) {
@@ -2021,7 +2017,7 @@
 
         void loadUserRecents(int userId) {
             synchronized (mService) {
-                mService.mRecentTasks.loadUserRecentsLocked(userId);
+                mService.getRecentTasks().loadUserRecentsLocked(userId);
             }
         }
 
diff --git a/com/android/server/appwidget/AppWidgetServiceImpl.java b/com/android/server/appwidget/AppWidgetServiceImpl.java
index 76e7782..6c15438 100644
--- a/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -107,8 +107,6 @@
 import com.android.server.WidgetBackupProvider;
 import com.android.server.policy.IconUtilities;
 
-import libcore.io.IoUtils;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -174,21 +172,27 @@
                 Slog.i(TAG, "Received broadcast: " + action + " on user " + userId);
             }
 
-            if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
-                onConfigurationChanged();
-            } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
-                    || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
-                synchronized (mLock) {
-                    reloadWidgetsMaskedState(userId);
-                }
-            } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) {
-                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                updateWidgetPackageSuspensionMaskedState(packages, true, getSendingUserId());
-            } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) {
-                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                updateWidgetPackageSuspensionMaskedState(packages, false, getSendingUserId());
-            } else {
-                onPackageBroadcastReceived(intent, userId);
+            switch (action) {
+                case Intent.ACTION_CONFIGURATION_CHANGED:
+                    onConfigurationChanged();
+                    break;
+                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+                    synchronized (mLock) {
+                        reloadWidgetsMaskedState(userId);
+                    }
+                    break;
+                case Intent.ACTION_PACKAGES_SUSPENDED:
+                    onPackageBroadcastReceived(intent, getSendingUserId());
+                    updateWidgetPackageSuspensionMaskedState(intent, true, getSendingUserId());
+                    break;
+                case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                    onPackageBroadcastReceived(intent, getSendingUserId());
+                    updateWidgetPackageSuspensionMaskedState(intent, false, getSendingUserId());
+                    break;
+                default:
+                    onPackageBroadcastReceived(intent, getSendingUserId());
+                    break;
             }
         }
     };
@@ -378,25 +382,32 @@
         boolean changed = false;
         boolean componentsModified = false;
 
-        String pkgList[] = null;
-        if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-            pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            added = true;
-        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-            pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            added = false;
-        } else {
-            Uri uri = intent.getData();
-            if (uri == null) {
-                return;
+        final String pkgList[];
+        switch (action) {
+            case Intent.ACTION_PACKAGES_SUSPENDED:
+            case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                changed = true;
+                break;
+            case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+                added = true;
+                // Follow through
+            case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                break;
+            default: {
+                Uri uri = intent.getData();
+                if (uri == null) {
+                    return;
+                }
+                String pkgName = uri.getSchemeSpecificPart();
+                if (pkgName == null) {
+                    return;
+                }
+                pkgList = new String[] { pkgName };
+                added = Intent.ACTION_PACKAGE_ADDED.equals(action);
+                changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
             }
-            String pkgName = uri.getSchemeSpecificPart();
-            if (pkgName == null) {
-                return;
-            }
-            pkgList = new String[] { pkgName };
-            added = Intent.ACTION_PACKAGE_ADDED.equals(action);
-            changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
         }
         if (pkgList == null || pkgList.length == 0) {
             return;
@@ -516,12 +527,13 @@
     /**
      * Incrementally update the masked state due to package suspension state.
      */
-    private void updateWidgetPackageSuspensionMaskedState(String[] packagesArray, boolean suspended,
+    private void updateWidgetPackageSuspensionMaskedState(Intent intent, boolean suspended,
             int profileId) {
+        String[] packagesArray = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
         if (packagesArray == null) {
             return;
         }
-        Set<String> packages = new ArraySet<String>(Arrays.asList(packagesArray));
+        Set<String> packages = new ArraySet<>(Arrays.asList(packagesArray));
         synchronized (mLock) {
             final int N = mProviders.size();
             for (int i = 0; i < N; i++) {
@@ -2630,11 +2642,9 @@
 
             // No file written for this user - nothing to do.
             AtomicFile file = getSavedStateFile(profileId);
-            try {
-                FileInputStream stream = file.openRead();
+            try (FileInputStream stream = file.openRead()) {
                 version = readProfileStateFromFileLocked(stream, profileId, loadedWidgets);
-                IoUtils.closeQuietly(stream);
-            } catch (FileNotFoundException e) {
+            } catch (IOException e) {
                 Slog.w(TAG, "Failed to read state: " + e);
             }
         }
diff --git a/com/android/server/autofill/AutofillManagerService.java b/com/android/server/autofill/AutofillManagerService.java
index 1f4161a..6c3eb20 100644
--- a/com/android/server/autofill/AutofillManagerService.java
+++ b/com/android/server/autofill/AutofillManagerService.java
@@ -533,12 +533,13 @@
         @Override
         public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
                 Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
-                String packageName) {
+                ComponentName componentName) {
 
             activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
             appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
             autofillId = Preconditions.checkNotNull(autofillId, "autoFillId");
-            packageName = Preconditions.checkNotNull(packageName, "packageName");
+            componentName = Preconditions.checkNotNull(componentName, "componentName");
+            final String packageName = Preconditions.checkNotNull(componentName.getPackageName());
 
             Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId");
 
@@ -551,7 +552,7 @@
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
                 return service.startSessionLocked(activityToken, getCallingUid(), appCallback,
-                        autofillId, bounds, value, hasCallback, flags, packageName);
+                        autofillId, bounds, value, hasCallback, flags, componentName);
             }
         }
 
@@ -603,7 +604,8 @@
         @Override
         public int updateOrRestartSession(IBinder activityToken, IBinder appCallback,
                 AutofillId autoFillId, Rect bounds, AutofillValue value, int userId,
-                boolean hasCallback, int flags, String packageName, int sessionId, int action) {
+                boolean hasCallback, int flags, ComponentName componentName, int sessionId,
+                int action) {
             boolean restart = false;
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
@@ -614,7 +616,7 @@
             }
             if (restart) {
                 return startSession(activityToken, appCallback, autoFillId, bounds, value, userId,
-                        hasCallback, flags, packageName);
+                        hasCallback, flags, componentName);
             }
 
             // Nothing changed...
diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java
index 075c741..2ed5eee 100644
--- a/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -35,7 +35,6 @@
 import android.content.pm.ServiceInfo;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.metrics.LogMaker;
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Bundle;
@@ -43,6 +42,7 @@
 import android.os.Looper;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.service.autofill.AutofillService;
@@ -58,6 +58,7 @@
 import android.util.LocalLog;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimeUtils;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
@@ -104,6 +105,17 @@
     private final LocalLog mUiLatencyHistory;
 
     /**
+     * Apps disabled by the service; key is package name, value is when they will be enabled again.
+     */
+    private ArrayMap<String, Long> mDisabledApps;
+
+    /**
+     * Activities disabled by the service; key is component name, value is when they will be enabled
+     * again.
+     */
+    private ArrayMap<ComponentName, Long> mDisabledActivities;
+
+    /**
      * Whether service was disabled for user due to {@link UserManager} restrictions.
      */
     private boolean mDisabled;
@@ -286,25 +298,46 @@
     int startSessionLocked(@NonNull IBinder activityToken, int uid,
             @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
             @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
-            int flags, @NonNull String packageName) {
+            int flags, @NonNull ComponentName componentName) {
         if (!isEnabled()) {
             return 0;
         }
+
+        final String shortComponentName = componentName.toShortString();
+
+        if (isAutofillDisabledLocked(componentName)) {
+            if (sDebug) {
+                Slog.d(TAG, "startSession(" + shortComponentName
+                        + "): ignored because disabled by service");
+            }
+
+            final IAutoFillManagerClient client = IAutoFillManagerClient.Stub
+                    .asInterface(appCallbackToken);
+            try {
+                client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Could not notify " + shortComponentName + " that it's disabled: " + e);
+            }
+
+            return NO_SESSION;
+        }
+
         if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags);
 
         // Occasionally clean up abandoned sessions
         pruneAbandonedSessionsLocked();
 
         final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken,
-                hasCallback, packageName);
+                hasCallback, componentName);
         if (newSession == null) {
             return NO_SESSION;
         }
 
         final String historyItem =
-                "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName
-                        + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" +
-                        hasCallback + " f=" + flags;
+                "id=" + newSession.id + " uid=" + uid + " a=" + shortComponentName
+                + " s=" + mInfo.getServiceInfo().packageName
+                + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds
+                + " hc=" + hasCallback + " f=" + flags;
         mRequestsHistory.log(historyItem);
 
         newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
@@ -395,7 +428,8 @@
     }
 
     private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
-            @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) {
+            @NonNull IBinder appCallbackToken, boolean hasCallback,
+            @NonNull ComponentName componentName) {
         // use random ids so that one app cannot know that another app creates sessions
         int sessionId;
         int tries = 0;
@@ -411,7 +445,7 @@
 
         final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
                 sessionId, uid, activityToken, appCallbackToken, hasCallback,
-                mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), packageName);
+                mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName);
         mSessions.put(newSession.id, newSession);
 
         return newSession;
@@ -664,6 +698,46 @@
         pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
         pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
 
+        pw.print(prefix); pw.print("Disabled apps: ");
+
+        if (mDisabledApps == null) {
+            pw.println("N/A");
+        } else {
+            final int size = mDisabledApps.size();
+            pw.println(size);
+            final StringBuilder builder = new StringBuilder();
+            final long now = SystemClock.elapsedRealtime();
+            for (int i = 0; i < size; i++) {
+                final String packageName = mDisabledApps.keyAt(i);
+                final long expiration = mDisabledApps.valueAt(i);
+                 builder.append(prefix).append(prefix)
+                     .append(i).append(". ").append(packageName).append(": ");
+                 TimeUtils.formatDuration((expiration - now), builder);
+                 builder.append('\n');
+             }
+             pw.println(builder);
+        }
+
+        pw.print(prefix); pw.print("Disabled activities: ");
+
+        if (mDisabledActivities == null) {
+            pw.println("N/A");
+        } else {
+            final int size = mDisabledActivities.size();
+            pw.println(size);
+            final StringBuilder builder = new StringBuilder();
+            final long now = SystemClock.elapsedRealtime();
+            for (int i = 0; i < size; i++) {
+                final ComponentName component = mDisabledActivities.keyAt(i);
+                final long expiration = mDisabledActivities.valueAt(i);
+                 builder.append(prefix).append(prefix)
+                     .append(i).append(". ").append(component).append(": ");
+                 TimeUtils.formatDuration((expiration - now), builder);
+                 builder.append('\n');
+             }
+             pw.println(builder);
+        }
+
         final int size = mSessions.size();
         if (size == 0) {
             pw.print(prefix); pw.println("No sessions");
@@ -764,6 +838,87 @@
         return mSetupComplete && mInfo != null && !mDisabled;
     }
 
+    /**
+     * Called by {@link Session} when service asked to disable autofill for an app.
+     */
+    void disableAutofillForApp(@NonNull String packageName, long duration) {
+        synchronized (mLock) {
+            if (mDisabledApps == null) {
+                mDisabledApps = new ArrayMap<>(1);
+            }
+            long expiration = SystemClock.elapsedRealtime() + duration;
+            // Protect it against overflow
+            if (expiration < 0) {
+                expiration = Long.MAX_VALUE;
+            }
+            mDisabledApps.put(packageName, expiration);
+            int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
+            mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP,
+                    packageName, getServicePackageName())
+                    .setCounterValue(intDuration));
+        }
+    }
+
+    /**
+     * Called by {@link Session} when service asked to disable autofill an app.
+     */
+    void disableAutofillForActivity(@NonNull ComponentName componentName, long duration) {
+        synchronized (mLock) {
+            if (mDisabledActivities == null) {
+                mDisabledActivities = new ArrayMap<>(1);
+            }
+            long expiration = SystemClock.elapsedRealtime() + duration;
+            // Protect it against overflow
+            if (expiration < 0) {
+                expiration = Long.MAX_VALUE;
+            }
+            mDisabledActivities.put(componentName, expiration);
+            int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
+            mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY,
+                    componentName.getPackageName(), getServicePackageName())
+                    .addTaggedData(MetricsEvent.FIELD_CLASS_NAME, componentName.getClassName())
+                    .setCounterValue(intDuration));
+        }
+    }
+
+    /**
+     * Checks if autofill is disabled by service to the given activity.
+     */
+    private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
+        // Check activities first.
+        long elapsedTime = 0;
+        if (mDisabledActivities != null) {
+            elapsedTime = SystemClock.elapsedRealtime();
+            final Long expiration = mDisabledActivities.get(componentName);
+            if (expiration != null) {
+                if (expiration >= elapsedTime) return true;
+                // Restriction expired - clean it up.
+                if (sVerbose) {
+                    Slog.v(TAG, "Removing " + componentName.toShortString() + " from disabled list");
+                }
+                mDisabledActivities.remove(componentName);
+            }
+        }
+
+        // Then check apps.
+        final String packageName = componentName.getPackageName();
+        if (mDisabledApps == null) return false;
+
+        final Long expiration = mDisabledApps.get(packageName);
+        if (expiration == null) return false;
+
+        if (elapsedTime == 0) {
+            elapsedTime = SystemClock.elapsedRealtime();
+        }
+
+        if (expiration >= elapsedTime) return true;
+
+        // Restriction expired - clean it up.
+        if (sVerbose)  Slog.v(TAG, "Removing " + packageName + " from disabled list");
+        mDisabledApps.remove(packageName);
+        return false;
+    }
+
     @Override
     public String toString() {
         return "AutofillManagerServiceImpl: [userId=" + mUserId
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index b720f74..010995f 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -54,14 +54,12 @@
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillContext;
-import android.service.autofill.FillEventHistory;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
 import android.service.autofill.InternalSanitizer;
 import android.service.autofill.InternalValidator;
 import android.service.autofill.SaveInfo;
 import android.service.autofill.SaveRequest;
-import android.service.autofill.Transformation;
 import android.service.autofill.ValueFinder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -91,7 +89,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -130,8 +127,8 @@
     @GuardedBy("mLock")
     @NonNull private IBinder mActivityToken;
 
-    /** Package name of the app that is auto-filled */
-    @NonNull private final String mPackageName;
+    /** Component that's being auto-filled */
+    @NonNull private final ComponentName mComponentName;
 
     @GuardedBy("mLock")
     private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>();
@@ -425,7 +422,7 @@
             @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
             @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
             @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
-            @NonNull ComponentName componentName, @NonNull String packageName) {
+            @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName) {
         id = sessionId;
         this.uid = uid;
         mStartTime = SystemClock.elapsedRealtime();
@@ -433,11 +430,11 @@
         mLock = lock;
         mUi = ui;
         mHandlerCaller = handlerCaller;
-        mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
+        mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this);
         mActivityToken = activityToken;
         mHasCallback = hasCallback;
         mUiLatencyHistory = uiLatencyHistory;
-        mPackageName = packageName;
+        mComponentName = componentName;
         mClient = IAutoFillManagerClient.Stub.asInterface(client);
 
         writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED);
@@ -483,18 +480,39 @@
                         + id + " destroyed");
                 return;
             }
-        }
-        if (response == null) {
-            processNullResponseLocked(requestFlags);
-            return;
+            if (response == null) {
+                processNullResponseLocked(requestFlags);
+                return;
+            }
         }
 
         mService.setLastResponse(serviceUid, id, response);
 
-        if ((response.getDatasets() == null || response.getDatasets().isEmpty())
-                        && response.getAuthentication() == null) {
+        int sessionFinishedState = 0;
+        final long disableDuration = response.getDisableDuration();
+        if (disableDuration > 0) {
+            final int flags = response.getFlags();
+            if (sDebug) {
+                final StringBuilder message = new StringBuilder("Service disabled autofill for ")
+                        .append(mComponentName)
+                        .append(": flags=").append(flags)
+                        .append(", duration=");
+                TimeUtils.formatDuration(disableDuration, message);
+                Slog.d(TAG, message.toString());
+            }
+            if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) {
+                mService.disableAutofillForActivity(mComponentName, disableDuration);
+            } else {
+                mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration);
+            }
+            sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE;
+        }
+
+        if (((response.getDatasets() == null || response.getDatasets().isEmpty())
+                        && response.getAuthentication() == null)
+                || disableDuration > 0) {
             // Response is "empty" from an UI point of view, need to notify client.
-            notifyUnavailableToClient(false);
+            notifyUnavailableToClient(sessionFinishedState);
         }
         synchronized (mLock) {
             processResponseLocked(response, null, requestFlags);
@@ -1216,7 +1234,8 @@
                 final IAutoFillManagerClient client = getClient();
                 mPendingSaveUi = new PendingUi(mActivityToken, id, client);
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
-                        mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this,
+                        mService.getServicePackageName(), saveInfo, valueFinder,
+                        mComponentName.getPackageName(), this,
                         mPendingSaveUi);
                 if (client != null) {
                     try {
@@ -1620,7 +1639,7 @@
         }
 
         getUiForShowing().showFillUi(filledId, response, filterText,
-                mService.getServicePackageName(), mPackageName, this);
+                mService.getServicePackageName(), mComponentName.getPackageName(), this);
 
         synchronized (mLock) {
             if (mUiShownTime == 0) {
@@ -1660,14 +1679,14 @@
         }
     }
 
-    private void notifyUnavailableToClient(boolean sessionFinished) {
+    private void notifyUnavailableToClient(int sessionFinishedState) {
         synchronized (mLock) {
             if (mCurrentViewId == null) return;
             try {
                 if (mHasCallback) {
-                    mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinished);
-                } else if (sessionFinished) {
-                    mClient.setSessionFinished(AutofillManager.STATE_FINISHED);
+                    mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState);
+                } else if (sessionFinishedState != 0) {
+                    mClient.setSessionFinished(sessionFinishedState);
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
@@ -1766,7 +1785,7 @@
         }
         mService.resetLastResponse();
         // Nothing to be done, but need to notify client.
-        notifyUnavailableToClient(true);
+        notifyUnavailableToClient(AutofillManager.STATE_FINISHED);
         removeSelf();
     }
 
@@ -1964,14 +1983,14 @@
 
     @Override
     public String toString() {
-        return "Session: [id=" + id + ", pkg=" + mPackageName + "]";
+        return "Session: [id=" + id + ", component=" + mComponentName + "]";
     }
 
     void dumpLocked(String prefix, PrintWriter pw) {
         final String prefix2 = prefix + "  ";
         pw.print(prefix); pw.print("id: "); pw.println(id);
         pw.print(prefix); pw.print("uid: "); pw.println(uid);
-        pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName);
+        pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName);
         pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
         pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
         pw.print(prefix); pw.print("Time to show UI: ");
@@ -2201,7 +2220,7 @@
     }
 
     private LogMaker newLogMaker(int category, String servicePackageName) {
-        return Helper.newLogMaker(category, mPackageName, servicePackageName);
+        return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName);
     }
 
     private void writeLog(int category) {
diff --git a/com/android/server/autofill/ui/AutoFillUI.java b/com/android/server/autofill/ui/AutoFillUI.java
index 36b95fc..dc36518 100644
--- a/com/android/server/autofill/ui/AutoFillUI.java
+++ b/com/android/server/autofill/ui/AutoFillUI.java
@@ -325,14 +325,14 @@
     }
 
     /**
-     * Hides all UI affordances.
+     * Hides all autofill UIs.
      */
     public void hideAll(@Nullable AutoFillUiCallback callback) {
         mHandler.post(() -> hideAllUiThread(callback));
     }
 
     /**
-     * Destroy all UI affordances.
+     * Destroy all autofill UIs.
      */
     public void destroyAll(@Nullable PendingUi pendingSaveUi,
             @Nullable AutoFillUiCallback callback, boolean notifyClient) {
diff --git a/com/android/server/autofill/ui/PendingUi.java b/com/android/server/autofill/ui/PendingUi.java
index 0851d3b..d1dfb5c 100644
--- a/com/android/server/autofill/ui/PendingUi.java
+++ b/com/android/server/autofill/ui/PendingUi.java
@@ -21,7 +21,7 @@
 import android.view.autofill.IAutoFillManagerClient;
 
 /**
- * Helper class used to handle a pending Autofill affordance such as the Save UI.
+ * Helper class used to handle a pending Autofill UI such as the save UI.
  *
  * <p>This class is not thread safe.
  */
diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java
index f9213aa..622b842 100644
--- a/com/android/server/backup/BackupManagerService.java
+++ b/com/android/server/backup/BackupManagerService.java
@@ -2877,7 +2877,7 @@
                     // The backend reports that our dataset has been wiped.  Note this in
                     // the event log; the no-success code below will reset the backup
                     // state as well.
-                    EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
+                    EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName);
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Error in backup thread", e);
@@ -9781,7 +9781,8 @@
                     }
 
                     Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
-                    EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName());
+                    String transportDirName = transport.transportDirName();
+                    EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
                     long startRealtime = SystemClock.elapsedRealtime();
                     int status = transport.initializeDevice();
 
@@ -9794,7 +9795,7 @@
                         Slog.i(TAG, "Device init successful");
                         int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
                         EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
-                        resetBackupState(new File(mBaseStateDir, transport.transportDirName()));
+                        resetBackupState(new File(mBaseStateDir, transportDirName));
                         EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
                         synchronized (mQueueLock) {
                             recordInitPendingLocked(false, transportName);
diff --git a/com/android/server/backup/internal/PerformBackupTask.java b/com/android/server/backup/internal/PerformBackupTask.java
index 7a8a920..c0caa55 100644
--- a/com/android/server/backup/internal/PerformBackupTask.java
+++ b/com/android/server/backup/internal/PerformBackupTask.java
@@ -339,7 +339,7 @@
                 // The backend reports that our dataset has been wiped.  Note this in
                 // the event log; the no-success code below will reset the backup
                 // state as well.
-                EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
+                EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName);
             }
         } catch (Exception e) {
             Slog.e(TAG, "Error in backup thread", e);
diff --git a/com/android/server/backup/internal/PerformInitializeTask.java b/com/android/server/backup/internal/PerformInitializeTask.java
index 939b1ae..690922f 100644
--- a/com/android/server/backup/internal/PerformInitializeTask.java
+++ b/com/android/server/backup/internal/PerformInitializeTask.java
@@ -79,7 +79,8 @@
                 }
 
                 Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
-                EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName());
+                String transportDirName = transport.transportDirName();
+                EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
                 long startRealtime = SystemClock.elapsedRealtime();
                 int status = transport.initializeDevice();
 
@@ -94,7 +95,7 @@
                     EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
                     backupManagerService
                             .resetBackupState(new File(backupManagerService.getBaseStateDir(),
-                                    transport.transportDirName()));
+                                    transportDirName));
                     EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
                     synchronized (backupManagerService.getQueueLock()) {
                         backupManagerService.recordInitPendingLocked(false, transportName);
diff --git a/com/android/server/connectivity/DefaultNetworkMetrics.java b/com/android/server/connectivity/DefaultNetworkMetrics.java
new file mode 100644
index 0000000..8981db1
--- /dev/null
+++ b/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -0,0 +1,78 @@
+/*
+ * 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.connectivity;
+
+import android.net.LinkProperties;
+import android.net.metrics.DefaultNetworkEvent;
+import android.net.metrics.IpConnectivityLog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tracks events related to the default network for the purpose of default network metrics.
+ * {@hide}
+ */
+public class DefaultNetworkMetrics {
+
+    private static final int ROLLING_LOG_SIZE = 64;
+
+    // Event buffer used for metrics upload. The buffer is cleared when events are collected.
+    @GuardedBy("this")
+    private final List<DefaultNetworkEvent> mEvents = new ArrayList<>();
+
+    public synchronized void listEvents(PrintWriter pw) {
+        long localTimeMs = System.currentTimeMillis();
+        for (DefaultNetworkEvent ev : mEvents) {
+            pw.println(ev);
+        }
+    }
+
+    public synchronized void listEventsAsProto(PrintWriter pw) {
+        for (DefaultNetworkEvent ev : mEvents) {
+            pw.print(IpConnectivityEventBuilder.toProto(ev));
+        }
+    }
+
+    public synchronized void flushEvents(List<IpConnectivityEvent> out) {
+        for (DefaultNetworkEvent ev : mEvents) {
+            out.add(IpConnectivityEventBuilder.toProto(ev));
+        }
+        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();
+        }
+
+        mEvents.add(ev);
+    }
+}
diff --git a/com/android/server/connectivity/IpConnectivityEventBuilder.java b/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 67e7216..3d71ecb 100644
--- a/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -132,6 +132,18 @@
         return out;
     }
 
+    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);
+        out.setDefaultNetworkEvent(ev);
+        return out;
+    }
+
     private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) {
         final IpConnectivityEvent ev = new IpConnectivityEvent();
         ev.networkId = netId;
@@ -164,11 +176,6 @@
             return true;
         }
 
-        if (in instanceof DefaultNetworkEvent) {
-            setDefaultNetworkEvent(out, (DefaultNetworkEvent) in);
-            return true;
-        }
-
         if (in instanceof NetworkEvent) {
             setNetworkEvent(out, (NetworkEvent) in);
             return true;
@@ -225,16 +232,6 @@
         out.setIpReachabilityEvent(ipReachabilityEvent);
     }
 
-    private static void setDefaultNetworkEvent(IpConnectivityEvent out, DefaultNetworkEvent in) {
-        IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent =
-                new IpConnectivityLogClass.DefaultNetworkEvent();
-        defaultNetworkEvent.networkId = netIdOf(in.netId);
-        defaultNetworkEvent.previousNetworkId = netIdOf(in.prevNetId);
-        defaultNetworkEvent.transportTypes = in.transportTypes;
-        defaultNetworkEvent.previousNetworkIpSupport = ipSupportOf(in);
-        out.setDefaultNetworkEvent(defaultNetworkEvent);
-    }
-
     private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) {
         IpConnectivityLogClass.NetworkEvent networkEvent =
                 new IpConnectivityLogClass.NetworkEvent();
diff --git a/com/android/server/connectivity/IpConnectivityMetrics.java b/com/android/server/connectivity/IpConnectivityMetrics.java
index f2445fa..24217e6 100644
--- a/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -32,12 +32,15 @@
 import android.util.ArrayMap;
 import android.util.Base64;
 import android.util.Log;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.RingBuffer;
 import com.android.internal.util.TokenBucket;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -112,6 +115,9 @@
 
     private final ToIntFunction<Context> mCapacityGetter;
 
+    @VisibleForTesting
+    final DefaultNetworkMetrics mDefaultNetworkMetrics = new DefaultNetworkMetrics();
+
     public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
         super(ctx);
         mCapacityGetter = capacityGetter;
@@ -135,6 +141,8 @@
 
             publishBinderService(SERVICE_NAME, impl);
             publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener);
+
+            LocalServices.addService(Logger.class, new LoggerImpl());
         }
     }
 
@@ -188,6 +196,8 @@
 
         final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);
 
+        mDefaultNetworkMetrics.flushEvents(protoEvents);
+
         if (mNetdListener != null) {
             mNetdListener.flushStatistics(protoEvents);
         }
@@ -228,6 +238,7 @@
             if (mNetdListener != null) {
                 mNetdListener.listAsProtos(pw);
             }
+            mDefaultNetworkMetrics.listEventsAsProto(pw);
             return;
         }
 
@@ -237,6 +248,7 @@
         if (mNetdListener != null) {
             mNetdListener.list(pw);
         }
+        mDefaultNetworkMetrics.listEvents(pw);
     }
 
     /**
@@ -254,6 +266,7 @@
         if (mNetdListener != null) {
             mNetdListener.list(pw);
         }
+        mDefaultNetworkMetrics.listEvents(pw);
     }
 
     private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -366,4 +379,15 @@
         map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
         return map;
     }
+
+    /** Direct non-Binder interface for event producer clients within the system servers. */
+    public interface Logger {
+        DefaultNetworkMetrics defaultNetworkMetrics();
+    }
+
+    private class LoggerImpl implements Logger {
+        public DefaultNetworkMetrics defaultNetworkMetrics() {
+            return mDefaultNetworkMetrics;
+        }
+    }
 }
diff --git a/com/android/server/connectivity/Tethering.java b/com/android/server/connectivity/Tethering.java
index d7cd81f..59870cb 100644
--- a/com/android/server/connectivity/Tethering.java
+++ b/com/android/server/connectivity/Tethering.java
@@ -28,6 +28,7 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static com.android.server.ConnectivityService.SHORT_ARG;
 
 import android.app.Notification;
@@ -60,6 +61,7 @@
 import android.net.RouteInfo;
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
+import android.net.util.VersionedBroadcastListener;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Bundle;
@@ -68,6 +70,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
@@ -184,6 +187,8 @@
     // TODO: Figure out how to merge this and other downstream-tracking objects
     // into a single coherent structure.
     private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;
+    private final VersionedBroadcastListener mCarrierConfigChange;
+    // TODO: Delete SimChangeListener; it's obsolete.
     private final SimChangeListener mSimChange;
 
     private volatile TetheringConfiguration mConfig;
@@ -224,11 +229,26 @@
         mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
                 mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new HashSet<>();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
+        mCarrierConfigChange = new VersionedBroadcastListener(
+                "CarrierConfigChangeListener", mContext, smHandler, filter,
+                (Intent ignored) -> {
+                    mLog.log("OBSERVED carrier config change");
+                    reevaluateSimCardProvisioning();
+                });
+        // TODO: Remove SimChangeListener altogether. For now, we retain it
+        // for logging purposes in case we need to debug something that might
+        // be related to changing signals from ACTION_SIM_STATE_CHANGED to
+        // ACTION_CARRIER_CONFIG_CHANGED.
         mSimChange = new SimChangeListener(
-                mContext, smHandler, () -> reevaluateSimCardProvisioning());
+                mContext, smHandler, () -> {
+                    mLog.log("OBSERVED SIM card change");
+                });
 
         mStateReceiver = new StateReceiver();
-        IntentFilter filter = new IntentFilter();
+        filter = new IntentFilter();
         filter.addAction(UsbManager.ACTION_USB_STATE);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
@@ -364,18 +384,30 @@
             return false;
         }
 
+        if (carrierConfigAffirmsEntitlementCheckNotRequired()) {
+            return false;
+        }
+        return (provisionApp.length == 2);
+    }
+
+    // The logic here is aimed solely at confirming that a CarrierConfig exists
+    // and affirms that entitlement checks are not required.
+    //
+    // TODO: find a better way to express this, or alter the checking process
+    // entirely so that this is more intuitive.
+    private boolean carrierConfigAffirmsEntitlementCheckNotRequired() {
         // Check carrier config for entitlement checks
         final CarrierConfigManager configManager = (CarrierConfigManager) mContext
              .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null && configManager.getConfig() != null) {
-            // we do have a CarrierConfigManager and it has a config.
-            boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
-                    CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
-            if (!isEntitlementCheckRequired) {
-                return false;
-            }
-        }
-        return (provisionApp.length == 2);
+        if (configManager == null) return false;
+
+        final PersistableBundle carrierConfig = configManager.getConfig();
+        if (carrierConfig == null) return false;
+
+        // A CarrierConfigManager was found and it has a config.
+        final boolean isEntitlementCheckRequired = carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+        return !isEntitlementCheckRequired;
     }
 
     // Used by the SIM card change observation code.
@@ -818,6 +850,7 @@
             } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
                 handleWifiApAction(intent);
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+                mLog.log("OBSERVED configuration changed");
                 updateConfiguration();
             }
         }
@@ -1192,6 +1225,7 @@
 
     private void reevaluateSimCardProvisioning() {
         if (!hasMobileHotspotProvisionApp()) return;
+        if (carrierConfigAffirmsEntitlementCheckNotRequired()) return;
 
         ArrayList<Integer> tethered = new ArrayList<>();
         synchronized (mPublicSync) {
@@ -1559,6 +1593,7 @@
                     return;
                 }
 
+                mCarrierConfigChange.startListening();
                 mSimChange.startListening();
                 mUpstreamNetworkMonitor.start();
 
@@ -1576,6 +1611,7 @@
                 mOffload.stop();
                 mUpstreamNetworkMonitor.stop();
                 mSimChange.stopListening();
+                mCarrierConfigChange.stopListening();
                 notifyDownstreamsOfNewUpstreamIface(null);
                 handleNewUpstreamNetworkState(null);
             }
diff --git a/com/android/server/connectivity/tethering/SimChangeListener.java b/com/android/server/connectivity/tethering/SimChangeListener.java
index 3e60f9f..33c9355 100644
--- a/com/android/server/connectivity/tethering/SimChangeListener.java
+++ b/com/android/server/connectivity/tethering/SimChangeListener.java
@@ -23,12 +23,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.util.VersionedBroadcastListener;
+import android.net.util.VersionedBroadcastListener.IntentCallback;
 import android.os.Handler;
 import android.util.Log;
 
 import com.android.internal.telephony.TelephonyIntents;
 
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 
 /**
@@ -37,88 +40,40 @@
  *
  * @hide
  */
-public class SimChangeListener {
+public class SimChangeListener extends VersionedBroadcastListener {
     private static final String TAG = SimChangeListener.class.getSimpleName();
     private static final boolean DBG = false;
 
-    private final Context mContext;
-    private final Handler mTarget;
-    private final AtomicInteger mSimBcastGenerationNumber;
-    private final Runnable mCallback;
-    private BroadcastReceiver mBroadcastReceiver;
-
     public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) {
-        mContext = ctx;
-        mTarget = handler;
-        mCallback = onSimCardLoadedCallback;
-        mSimBcastGenerationNumber = new AtomicInteger(0);
+        super(TAG, ctx, handler, makeIntentFilter(), makeCallback(onSimCardLoadedCallback));
     }
 
-    public int generationNumber() {
-        return mSimBcastGenerationNumber.get();
-    }
-
-    public void startListening() {
-        if (DBG) Log.d(TAG, "startListening for SIM changes");
-
-        if (mBroadcastReceiver != null) return;
-
-        mBroadcastReceiver = new SimChangeBroadcastReceiver(
-                mSimBcastGenerationNumber.incrementAndGet());
+    private static IntentFilter makeIntentFilter() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-
-        mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget);
+        return filter;
     }
 
-    public void stopListening() {
-        if (DBG) Log.d(TAG, "stopListening for SIM changes");
+    private static Consumer<Intent> makeCallback(Runnable onSimCardLoadedCallback) {
+        return new Consumer<Intent>() {
+            private boolean mSimNotLoadedSeen = false;
 
-        if (mBroadcastReceiver == null) return;
+            @Override
+            public void accept(Intent intent) {
+                final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
+                Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
+                        mSimNotLoadedSeen);
 
-        mSimBcastGenerationNumber.incrementAndGet();
-        mContext.unregisterReceiver(mBroadcastReceiver);
-        mBroadcastReceiver = null;
-    }
+                if (!INTENT_VALUE_ICC_LOADED.equals(state)) {
+                    mSimNotLoadedSeen = true;
+                    return;
+                }
 
-    private boolean isSimCardLoaded(String state) {
-        return INTENT_VALUE_ICC_LOADED.equals(state);
-    }
-
-    private class SimChangeBroadcastReceiver extends BroadcastReceiver {
-        // used to verify this receiver is still current
-        final private int mGenerationNumber;
-
-        // used to check the sim state transition from non-loaded to loaded
-        private boolean mSimNotLoadedSeen = false;
-
-        public SimChangeBroadcastReceiver(int generationNumber) {
-            mGenerationNumber = generationNumber;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final int currentGenerationNumber = mSimBcastGenerationNumber.get();
-
-            if (DBG) {
-                Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
-                        ", current generationNumber=" + currentGenerationNumber);
+                if (mSimNotLoadedSeen) {
+                    mSimNotLoadedSeen = false;
+                    onSimCardLoadedCallback.run();
+                }
             }
-            if (mGenerationNumber != currentGenerationNumber) return;
-
-            final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
-            Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
-                    mSimNotLoadedSeen);
-
-            if (!isSimCardLoaded(state)) {
-                mSimNotLoadedSeen = true;
-                return;
-            }
-
-            if (mSimNotLoadedSeen) {
-                mSimNotLoadedSeen = false;
-                mCallback.run();
-            }
-        }
+        };
     }
 }
diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 80f6a4b..2d8a0ee 100644
--- a/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.BIND_DEVICE_ADMIN;
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
 import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
 import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
@@ -225,6 +226,8 @@
 
     private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
 
+    private static final String TAG_LOCK_TASK_FEATURES = "lock-task-features";
+
     private static final String TAG_STATUS_BAR = "statusbar";
 
     private static final String ATTR_DISABLED = "disabled";
@@ -507,6 +510,9 @@
         // This is the list of component allowed to start lock task mode.
         List<String> mLockTaskPackages = new ArrayList<>();
 
+        // Bitfield of feature flags to be enabled during LockTask mode.
+        int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+
         boolean mStatusBarDisabled = false;
 
         ComponentName mRestrictionsProvider;
@@ -2628,6 +2634,12 @@
                 out.endTag(null, TAG_LOCK_TASK_COMPONENTS);
             }
 
+            if (policy.mLockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
+                out.startTag(null, TAG_LOCK_TASK_FEATURES);
+                out.attribute(null, ATTR_VALUE, Integer.toString(policy.mLockTaskFeatures));
+                out.endTag(null, TAG_LOCK_TASK_FEATURES);
+            }
+
             if (policy.mStatusBarDisabled) {
                 out.startTag(null, TAG_STATUS_BAR);
                 out.attribute(null, ATTR_DISABLED, Boolean.toString(policy.mStatusBarDisabled));
@@ -2868,6 +2880,9 @@
                     policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
                 } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
                     policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
+                } else if (TAG_LOCK_TASK_FEATURES.equals(tag)) {
+                    policy.mLockTaskFeatures = Integer.parseInt(
+                            parser.getAttributeValue(null, ATTR_VALUE));
                 } else if (TAG_STATUS_BAR.equals(tag)) {
                     policy.mStatusBarDisabled = Boolean.parseBoolean(
                             parser.getAttributeValue(null, ATTR_DISABLED));
@@ -2936,6 +2951,7 @@
         validatePasswordOwnerLocked(policy);
         updateMaximumTimeToLockLocked(userHandle);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
+        updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
         if (policy.mStatusBarDisabled) {
             setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
         }
@@ -2953,6 +2969,18 @@
         }
     }
 
+    private void updateLockTaskFeaturesLocked(int flags, int userId) {
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            mInjector.getIActivityManager()
+                    .updateLockTaskFeatures(userId, flags);
+        } catch (RemoteException e) {
+            // Not gonna happen.
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
     private void updateDeviceOwnerLocked() {
         long ident = mInjector.binderClearCallingIdentity();
         try {
@@ -6939,6 +6967,7 @@
         policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
         policy.mAffiliationIds.clear();
         policy.mLockTaskPackages.clear();
+        policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
         saveSettingsLocked(userId);
 
         try {
@@ -8921,25 +8950,6 @@
         updateLockTaskPackagesLocked(packages, userHandle);
     }
 
-    private void maybeClearLockTaskPackagesLocked() {
-        final long ident = mInjector.binderClearCallingIdentity();
-        try {
-            final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
-            for (int i = 0; i < userInfos.size(); i++) {
-                int userId = userInfos.get(i).id;
-                final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
-                if (!lockTaskPackages.isEmpty() &&
-                        !isUserAffiliatedWithDeviceLocked(userId)) {
-                    Slog.d(LOG_TAG,
-                            "User id " + userId + " not affiliated. Clearing lock task packages");
-                    setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
-                }
-            }
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
-    }
-
     @Override
     public String[] getLockTaskPackages(ComponentName who) {
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -8966,12 +8976,82 @@
     }
 
     @Override
+    public void setLockTaskFeatures(ComponentName who, int flags) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        final int userHandle = mInjector.userHandleGetCallingUserId();
+        synchronized (this) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
+                throw new SecurityException("Admin " + who +
+                        " is neither the device owner or affiliated user's profile owner.");
+            }
+            setLockTaskFeaturesLocked(userHandle, flags);
+        }
+    }
+
+    private void setLockTaskFeaturesLocked(int userHandle, int flags) {
+        DevicePolicyData policy = getUserData(userHandle);
+        policy.mLockTaskFeatures = flags;
+        saveSettingsLocked(userHandle);
+        updateLockTaskFeaturesLocked(flags, userHandle);
+    }
+
+    @Override
+    public int getLockTaskFeatures(ComponentName who) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        final int userHandle = mInjector.userHandleGetCallingUserId();
+        synchronized (this) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
+                throw new SecurityException("Admin " + who +
+                        " is neither the device owner or affiliated user's profile owner.");
+            }
+            return getUserData(userHandle).mLockTaskFeatures;
+        }
+    }
+
+    private void maybeClearLockTaskPolicyLocked() {
+        final long ident = mInjector.binderClearCallingIdentity();
+        try {
+            final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
+            for (int i = userInfos.size() - 1; i >= 0; i--) {
+                int userId = userInfos.get(i).id;
+                if (isUserAffiliatedWithDeviceLocked(userId)) {
+                    continue;
+                }
+
+                final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
+                if (!lockTaskPackages.isEmpty()) {
+                    Slog.d(LOG_TAG,
+                            "User id " + userId + " not affiliated. Clearing lock task packages");
+                    setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
+                }
+                final int lockTaskFeatures = getUserData(userId).mLockTaskFeatures;
+                if (lockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE){
+                    Slog.d(LOG_TAG,
+                            "User id " + userId + " not affiliated. Clearing lock task features");
+                    setLockTaskFeaturesLocked(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+                }
+            }
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
         if (!isCallerWithSystemUid()) {
             throw new SecurityException("notifyLockTaskModeChanged can only be called by system");
         }
         synchronized (this) {
             final DevicePolicyData policy = getUserData(userHandle);
+
+            if (policy.mStatusBarDisabled) {
+                // Status bar is managed by LockTaskController during LockTask, so we cancel this
+                // policy when LockTask starts, and reapply it when LockTask ends
+                setStatusBarDisabledInternal(!isEnabled, userHandle);
+            }
+
             Bundle adminExtras = new Bundle();
             adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg);
             for (ActiveAdmin admin : policy.mAdminList) {
@@ -9182,8 +9262,17 @@
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
             DevicePolicyData policy = getUserData(userId);
             if (policy.mStatusBarDisabled != disabled) {
-                if (!setStatusBarDisabledInternal(disabled, userId)) {
-                    return false;
+                boolean isLockTaskMode = false;
+                try {
+                    isLockTaskMode = mInjector.getIActivityManager().getLockTaskModeState()
+                            != LOCK_TASK_MODE_NONE;
+                } catch (RemoteException e) {
+                    Slog.e(LOG_TAG, "Failed to get LockTask mode");
+                }
+                if (!isLockTaskMode) {
+                    if (!setStatusBarDisabledInternal(disabled, userId)) {
+                        return false;
+                    }
                 }
                 policy.mStatusBarDisabled = disabled;
                 saveSettingsLocked(userId);
@@ -10283,7 +10372,7 @@
             // but as a result of that other users might become affiliated or un-affiliated.
             maybePauseDeviceWideLoggingLocked();
             maybeResumeDeviceWideLoggingLocked();
-            maybeClearLockTaskPackagesLocked();
+            maybeClearLockTaskPolicyLocked();
         }
     }
 
diff --git a/com/android/server/display/ColorFade.java b/com/android/server/display/ColorFade.java
index 2541050..c2167eb 100644
--- a/com/android/server/display/ColorFade.java
+++ b/com/android/server/display/ColorFade.java
@@ -585,9 +585,11 @@
                     } else {
                         flags = SurfaceControl.OPAQUE | SurfaceControl.HIDDEN;
                     }
-                    mSurfaceControl = new SurfaceControl(mSurfaceSession,
-                            "ColorFade", mDisplayWidth, mDisplayHeight,
-                            PixelFormat.OPAQUE, flags);
+                    mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+                            .setName("ColorFade")
+                            .setSize(mDisplayWidth, mDisplayHeight)
+                            .setFlags(flags)
+                            .build();
                 } catch (OutOfResourcesException ex) {
                     Slog.e(TAG, "Unable to create surface.", ex);
                     return false;
diff --git a/com/android/server/display/DisplayTransformManager.java b/com/android/server/display/DisplayTransformManager.java
index dbbb318..bef6898 100644
--- a/com/android/server/display/DisplayTransformManager.java
+++ b/com/android/server/display/DisplayTransformManager.java
@@ -16,15 +16,20 @@
 
 package com.android.server.display;
 
+import android.app.ActivityManager;
+import android.app.IActivityManager;
 import android.opengl.Matrix;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
 
+import com.android.internal.app.NightDisplayController;
 import java.util.Arrays;
 
 /**
@@ -34,6 +39,8 @@
 
     private static final String TAG = "DisplayTransformManager";
 
+    private static final String SURFACE_FLINGER = "SurfaceFlinger";
+
     /**
      * Color transform level used by Night display to tint the display red.
      */
@@ -50,6 +57,15 @@
     private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
     private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
 
+    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 static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
+    private static final int SURFACE_FLINGER_TRANSACTION_NATIVE_MODE = 1023;
+
+    private static final float COLOR_SATURATION_NATURAL = 1.0f;
+    private static final float COLOR_SATURATION_BOOSTED = 1.1f;
+
     /**
      * Map of level -> color transformation matrix.
      */
@@ -161,7 +177,7 @@
      * Propagates the provided color transformation matrix to the SurfaceFlinger.
      */
     private static void applyColorMatrix(float[] m) {
-        final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
         if (flinger != null) {
             final Parcel data = Parcel.obtain();
             data.writeInterfaceToken("android.ui.ISurfaceComposer");
@@ -187,7 +203,7 @@
      * Propagates the provided Daltonization mode to the SurfaceFlinger.
      */
     private static void applyDaltonizerMode(int mode) {
-        final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
         if (flinger != null) {
             final Parcel data = Parcel.obtain();
             data.writeInterfaceToken("android.ui.ISurfaceComposer");
@@ -201,4 +217,73 @@
             }
         }
     }
+
+    public static boolean isNativeModeEnabled() {
+        return SystemProperties.getBoolean(PERSISTENT_PROPERTY_NATIVE_MODE, false);
+    }
+
+    public boolean setColorMode(int colorMode) {
+        if (colorMode == NightDisplayController.COLOR_MODE_NATURAL) {
+            applySaturation(COLOR_SATURATION_NATURAL);
+            setNativeMode(false);
+        } else if (colorMode == NightDisplayController.COLOR_MODE_BOOSTED) {
+            applySaturation(COLOR_SATURATION_BOOSTED);
+            setNativeMode(false);
+        } else if (colorMode == NightDisplayController.COLOR_MODE_SATURATED) {
+            applySaturation(COLOR_SATURATION_NATURAL);
+            setNativeMode(true);
+        }
+
+        updateConfiguration();
+
+        return true;
+    }
+
+    /**
+     * Propagates the provided saturation to the SurfaceFlinger.
+     */
+    private void applySaturation(float saturation) {
+        SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation));
+        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
+        if (flinger != null) {
+            final Parcel data = Parcel.obtain();
+            data.writeInterfaceToken("android.ui.ISurfaceComposer");
+            data.writeFloat(saturation);
+            try {
+                flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to set saturation", ex);
+            } finally {
+                data.recycle();
+            }
+        }
+    }
+
+    /**
+     * Toggles native mode on/off in SurfaceFlinger.
+     */
+    private void setNativeMode(boolean enabled) {
+        SystemProperties.set(PERSISTENT_PROPERTY_NATIVE_MODE, enabled ? "1" : "0");
+        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
+        if (flinger != null) {
+            final Parcel data = Parcel.obtain();
+            data.writeInterfaceToken("android.ui.ISurfaceComposer");
+            data.writeInt(enabled ? 1 : 0);
+            try {
+                flinger.transact(SURFACE_FLINGER_TRANSACTION_NATIVE_MODE, data, null, 0);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to set native mode", ex);
+            } finally {
+                data.recycle();
+            }
+        }
+    }
+
+    private void updateConfiguration() {
+        try {
+            ActivityManager.getService().updateConfiguration(null);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not update configuration", e);
+        }
+    }
 }
diff --git a/com/android/server/display/NightDisplayService.java b/com/android/server/display/NightDisplayService.java
index 9cf1367..a7c3ff9 100644
--- a/com/android/server/display/NightDisplayService.java
+++ b/com/android/server/display/NightDisplayService.java
@@ -52,7 +52,8 @@
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.TimeZone;
+
+import com.android.internal.R;
 
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
 
@@ -126,12 +127,6 @@
     public NightDisplayService(Context context) {
         super(context);
         mHandler = new Handler(Looper.getMainLooper());
-
-        final String[] coefficients = context.getResources().getStringArray(
-                com.android.internal.R.array.config_nightDisplayColorTemperatureCoefficients);
-        for (int i = 0; i < 9 && i < coefficients.length; i++) {
-            mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
-        }
     }
 
     @Override
@@ -236,6 +231,8 @@
         mController = new NightDisplayController(getContext(), mCurrentUser);
         mController.setListener(this);
 
+        setCoefficientMatrix(getContext());
+
         // Prepare color transformation matrix.
         setMatrix(mController.getColorTemperature(), mMatrixNight);
 
@@ -331,6 +328,26 @@
         applyTint(true);
     }
 
+    @Override
+    public void onDisplayColorModeChanged(int colorMode) {
+        final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+        dtm.setColorMode(colorMode);
+
+        setCoefficientMatrix(getContext());
+        setMatrix(mController.getColorTemperature(), mMatrixNight);
+        applyTint(true);
+    }
+
+    private void setCoefficientMatrix(Context context) {
+        final boolean isNative = DisplayTransformManager.isNativeModeEnabled();
+        final String[] coefficients = context.getResources().getStringArray(isNative
+                ? R.array.config_nightDisplayColorTemperatureCoefficientsNative
+                : R.array.config_nightDisplayColorTemperatureCoefficients);
+        for (int i = 0; i < 9 && i < coefficients.length; i++) {
+            mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
+        }
+    }
+
     /**
      * Applies current color temperature matrix, or removes it if deactivated.
      *
diff --git a/com/android/server/fingerprint/FingerprintService.java b/com/android/server/fingerprint/FingerprintService.java
index 1df9c86..d0d951b 100644
--- a/com/android/server/fingerprint/FingerprintService.java
+++ b/com/android/server/fingerprint/FingerprintService.java
@@ -59,9 +59,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.security.KeyStore;
-import android.service.fingerprint.FingerprintActionStatsProto;
-import android.service.fingerprint.FingerprintServiceDumpProto;
-import android.service.fingerprint.FingerprintUserStatsProto;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
@@ -1374,11 +1371,11 @@
             final PerformanceStats normal = mPerformanceMap.get(userId);
             if (normal != null) {
                 final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
-                proto.write(FingerprintActionStatsProto.ACCEPT, normal.accept);
-                proto.write(FingerprintActionStatsProto.REJECT, normal.reject);
-                proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire);
-                proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout);
-                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.permanentLockout);
+                proto.write(PerformanceStatsProto.ACCEPT, normal.accept);
+                proto.write(PerformanceStatsProto.REJECT, normal.reject);
+                proto.write(PerformanceStatsProto.ACQUIRE, normal.acquire);
+                proto.write(PerformanceStatsProto.LOCKOUT, normal.lockout);
+                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, normal.permanentLockout);
                 proto.end(countsToken);
             }
 
@@ -1387,11 +1384,11 @@
             final PerformanceStats crypto = mCryptoPerformanceMap.get(userId);
             if (crypto != null) {
                 final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
-                proto.write(FingerprintActionStatsProto.ACCEPT, crypto.accept);
-                proto.write(FingerprintActionStatsProto.REJECT, crypto.reject);
-                proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire);
-                proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout);
-                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.permanentLockout);
+                proto.write(PerformanceStatsProto.ACCEPT, crypto.accept);
+                proto.write(PerformanceStatsProto.REJECT, crypto.reject);
+                proto.write(PerformanceStatsProto.ACQUIRE, crypto.acquire);
+                proto.write(PerformanceStatsProto.LOCKOUT, crypto.lockout);
+                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, crypto.permanentLockout);
                 proto.end(countsToken);
             }
 
diff --git a/com/android/server/job/JobServiceContext.java b/com/android/server/job/JobServiceContext.java
index d3fd3a9..a7e674b 100644
--- a/com/android/server/job/JobServiceContext.java
+++ b/com/android/server/job/JobServiceContext.java
@@ -65,7 +65,7 @@
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
     private static final String TAG = "JobServiceContext";
     /** Amount of time a job is allowed to execute for before being considered timed-out. */
-    private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
+    public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
     /** Amount of time the JobScheduler waits for the initial service launch+bind. */
     private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
     /** Amount of time the JobScheduler will wait for a response from an app for a message. */
@@ -216,7 +216,7 @@
             final JobInfo ji = job.getJob();
             mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
-                    isDeadlineExpired, triggeredUris, triggeredAuthorities);
+                    isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
             mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
 
             // Once we'e begun executing a job, we by definition no longer care whether
diff --git a/com/android/server/job/controllers/ConnectivityController.java b/com/android/server/job/controllers/ConnectivityController.java
index 78367fe..ddee345 100644
--- a/com/android/server/job/controllers/ConnectivityController.java
+++ b/com/android/server/job/controllers/ConnectivityController.java
@@ -25,13 +25,16 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkPolicyManager;
+import android.net.TrafficStats;
 import android.os.Process;
 import android.os.UserHandle;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobServiceContext;
 import com.android.server.job.StateChangedListener;
 
 import java.io.PrintWriter;
@@ -99,18 +102,66 @@
         }
     }
 
+    /**
+     * Test to see if running the given job on the given network is sane.
+     * <p>
+     * For example, if a job is trying to send 10MB over a 128Kbps EDGE
+     * connection, it would take 10.4 minutes, and has no chance of succeeding
+     * before the job times out, so we'd be insane to try running it.
+     */
+    private boolean isSane(JobStatus jobStatus, NetworkCapabilities capabilities) {
+        final long estimatedBytes = jobStatus.getEstimatedNetworkBytes();
+        if (estimatedBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+            // We don't know how large the job is; cross our fingers!
+            return true;
+        }
+        if (capabilities == null) {
+            // We don't know what the network is like; cross our fingers!
+            return true;
+        }
+
+        // We don't ask developers to differentiate between upstream/downstream
+        // in their size estimates, so test against the slowest link direction.
+        final long downstream = capabilities.getLinkDownstreamBandwidthKbps();
+        final long upstream = capabilities.getLinkUpstreamBandwidthKbps();
+        final long slowest;
+        if (downstream > 0 && upstream > 0) {
+            slowest = Math.min(downstream, upstream);
+        } else if (downstream > 0) {
+            slowest = downstream;
+        } else if (upstream > 0) {
+            slowest = upstream;
+        } else {
+            // We don't know what the network is like; cross our fingers!
+            return true;
+        }
+
+        final long estimatedMillis = ((estimatedBytes * DateUtils.SECOND_IN_MILLIS)
+                / (slowest * TrafficStats.KB_IN_BYTES / 8));
+        if (estimatedMillis > JobServiceContext.EXECUTING_TIMESLICE_MILLIS) {
+            // If we'd never finish before the timeout, we'd be insane!
+            Slog.w(TAG, "Estimated " + estimatedBytes + " bytes over " + slowest
+                    + " kbps network would take " + estimatedMillis + "ms; that's insane!");
+            return false;
+        } else {
+            return true;
+        }
+    }
+
     private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
         final int jobUid = jobStatus.getSourceUid();
         final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
-        final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobUid, ignoreBlocked);
         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 boolean connected = (info != null) && info.isConnected();
         final boolean validated = (capabilities != null)
                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
-        final boolean connected = (info != null) && info.isConnected();
-        final boolean connectionUsable = connected && validated;
+        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);
@@ -125,6 +176,11 @@
         changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
         changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
 
+        // Pass along the evaluated network for job to use; prevents race
+        // conditions as default routes change over time, and opens the door to
+        // using non-default routes.
+        jobStatus.network = network;
+
         // Track system-uid connected/validated as a general reportable proxy for the
         // overall state of connectivity constraint satisfiability.
         if (jobUid == Process.SYSTEM_UID) {
diff --git a/com/android/server/job/controllers/JobStatus.java b/com/android/server/job/controllers/JobStatus.java
index 23caa8c..46ed84e 100644
--- a/com/android/server/job/controllers/JobStatus.java
+++ b/com/android/server/job/controllers/JobStatus.java
@@ -22,6 +22,7 @@
 import android.app.job.JobWorkItem;
 import android.content.ClipData;
 import android.content.ComponentName;
+import android.net.Network;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -167,6 +168,7 @@
     // These are filled in by controllers when preparing for execution.
     public ArraySet<Uri> changedUris;
     public ArraySet<String> changedAuthorities;
+    public Network network;
 
     public int lastEvaluatedPriority;
 
@@ -217,6 +219,8 @@
      */
     ContentObserverController.JobInstance contentObserverJobInstance;
 
+    private long totalNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+
     /** Provide a handle to the service that this job will be run on. */
     public int getServiceToken() {
         return callingUid;
@@ -294,6 +298,8 @@
 
         mLastSuccessfulRunTime = lastSuccessfulRunTime;
         mLastFailedRunTime = lastFailedRunTime;
+
+        updateEstimatedNetworkBytesLocked();
     }
 
     /** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -387,6 +393,7 @@
                     sourcePackageName, sourceUserId, toShortString()));
         }
         pendingWork.add(work);
+        updateEstimatedNetworkBytesLocked();
     }
 
     public JobWorkItem dequeueWorkLocked() {
@@ -399,6 +406,7 @@
                 executingWork.add(work);
                 work.bumpDeliveryCount();
             }
+            updateEstimatedNetworkBytesLocked();
             return work;
         }
         return null;
@@ -457,6 +465,7 @@
             pendingWork = null;
             executingWork = null;
             incomingJob.nextPendingWorkId = nextPendingWorkId;
+            incomingJob.updateEstimatedNetworkBytesLocked();
         } else {
             // We are completely stopping the job...  need to clean up work.
             ungrantWorkList(am, pendingWork);
@@ -464,6 +473,7 @@
             ungrantWorkList(am, executingWork);
             executingWork = null;
         }
+        updateEstimatedNetworkBytesLocked();
     }
 
     public void prepareLocked(IActivityManager am) {
@@ -566,6 +576,38 @@
         return job.getFlags();
     }
 
+    private void updateEstimatedNetworkBytesLocked() {
+        totalNetworkBytes = computeEstimatedNetworkBytesLocked();
+    }
+
+    private long computeEstimatedNetworkBytesLocked() {
+        // If any component of the job has unknown usage, we don't have a
+        // complete picture of what data will be used, and we have to treat the
+        // entire job as unknown.
+        long totalNetworkBytes = 0;
+        long networkBytes = job.getEstimatedNetworkBytes();
+        if (networkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+            return JobInfo.NETWORK_BYTES_UNKNOWN;
+        } else {
+            totalNetworkBytes += networkBytes;
+        }
+        if (pendingWork != null) {
+            for (int i = 0; i < pendingWork.size(); i++) {
+                networkBytes = pendingWork.get(i).getEstimatedNetworkBytes();
+                if (networkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    return JobInfo.NETWORK_BYTES_UNKNOWN;
+                } else {
+                    totalNetworkBytes += networkBytes;
+                }
+            }
+        }
+        return totalNetworkBytes;
+    }
+
+    public long getEstimatedNetworkBytes() {
+        return totalNetworkBytes;
+    }
+
     /** Does this job have any sort of networking constraint? */
     public boolean hasConnectivityConstraint() {
         return (requiredConstraints&CONNECTIVITY_MASK) != 0;
@@ -1045,6 +1087,9 @@
             if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
                 pw.print(prefix); pw.print("  Network type: "); pw.println(job.getNetworkType());
             }
+            if (totalNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                pw.print(prefix); pw.print("  Network bytes: "); pw.println(totalNetworkBytes);
+            }
             if (job.getMinLatencyMillis() != 0) {
                 pw.print(prefix); pw.print("  Minimum latency: ");
                 TimeUtils.formatDuration(job.getMinLatencyMillis(), pw);
@@ -1101,6 +1146,9 @@
                 }
             }
         }
+        if (network != null) {
+            pw.print(prefix); pw.print("Network: "); pw.println(network);
+        }
         if (pendingWork != null && pendingWork.size() > 0) {
             pw.print(prefix); pw.println("Pending work:");
             for (int i = 0; i < pendingWork.size(); i++) {
diff --git a/com/android/server/location/GnssLocationProvider.java b/com/android/server/location/GnssLocationProvider.java
index e41c17d..4cf35bc 100644
--- a/com/android/server/location/GnssLocationProvider.java
+++ b/com/android/server/location/GnssLocationProvider.java
@@ -669,9 +669,11 @@
         for (String item : configValues) {
             if (DEBUG) Log.d(TAG, "GpsParamsResource: " + item);
             // We need to support "KEY =", but not "=VALUE".
-            String[] split = item.split("=");
-            if (split.length == 2) {
-                properties.setProperty(split[0].trim().toUpperCase(), split[1]);
+            int index = item.indexOf("=");
+            if (index > 0 && index + 1 < item.length()) {
+                String key = item.substring(0, index);
+                String value = item.substring(index + 1);
+                properties.setProperty(key.trim().toUpperCase(), value);
             } else {
                 Log.w(TAG, "malformed contents: " + item);
             }
diff --git a/com/android/server/pm/BackgroundDexOptService.java b/com/android/server/pm/BackgroundDexOptService.java
index 6d8cac0..679250c 100644
--- a/com/android/server/pm/BackgroundDexOptService.java
+++ b/com/android/server/pm/BackgroundDexOptService.java
@@ -340,7 +340,8 @@
             int dexoptFlags =
                     DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
                     DexoptOptions.DEXOPT_BOOT_COMPLETE |
-                    (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0);
+                    (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) |
+                    DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
             if (is_for_primary_dex) {
                 int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
                         dexoptFlags));
diff --git a/com/android/server/pm/Installer.java b/com/android/server/pm/Installer.java
index 371b3ef..210eb13 100644
--- a/com/android/server/pm/Installer.java
+++ b/com/android/server/pm/Installer.java
@@ -56,6 +56,8 @@
     public static final int DEXOPT_STORAGE_CE     = 1 << 7;
     /** Indicates that the dex file passed to dexopt in on DE storage. */
     public static final int DEXOPT_STORAGE_DE     = 1 << 8;
+    /** Indicates that dexopt is invoked from the background service. */
+    public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
 
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
diff --git a/com/android/server/pm/OtaDexoptService.java b/com/android/server/pm/OtaDexoptService.java
index 6253857..03f662a 100644
--- a/com/android/server/pm/OtaDexoptService.java
+++ b/com/android/server/pm/OtaDexoptService.java
@@ -310,7 +310,7 @@
                 collectingInstaller, mPackageManagerService.mInstallLock, mContext);
 
         String[] libraryDependencies = pkg.usesLibraryFiles;
-        if (pkg.isSystemApp()) {
+        if (pkg.isSystem()) {
             // For system apps, we want to avoid classpaths checks.
             libraryDependencies = NO_LIBRARIES;
         }
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index cf0ffbb..86a1c03 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -54,6 +54,7 @@
 import static com.android.server.pm.Installer.DEXOPT_FORCE;
 import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
 import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
+import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 
@@ -110,7 +111,7 @@
         }
 
         // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
-        if (pkg.isPrivilegedApp()) {
+        if (pkg.isPrivileged()) {
             return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
         }
 
@@ -612,6 +613,9 @@
         if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) {
             flagsList.add("storage_de");
         }
+        if ((flags & DEXOPT_IDLE_BACKGROUND_JOB) == DEXOPT_IDLE_BACKGROUND_JOB) {
+            flagsList.add("idle_background_job");
+        }
 
         return String.join(",", flagsList);
     }
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index 391deb7..7be0cde 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -397,7 +397,7 @@
     static final boolean DEBUG_UPGRADE = false;
     static final boolean DEBUG_DOMAIN_VERIFICATION = false;
     private static final boolean DEBUG_BACKUP = false;
-    private static final boolean DEBUG_INSTALL = false;
+    public static final boolean DEBUG_INSTALL = false;
     public static final boolean DEBUG_REMOVE = false;
     private static final boolean DEBUG_BROADCASTS = false;
     private static final boolean DEBUG_SHOW_INFO = false;
@@ -522,7 +522,7 @@
      */
     private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW;
 
-    static final String PLATFORM_PACKAGE_NAME = "android";
+    public static final String PLATFORM_PACKAGE_NAME = "android";
 
     static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
 
@@ -542,18 +542,6 @@
 
     private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
 
-    /** Permission grant: not grant the permission. */
-    private static final int GRANT_DENIED = 1;
-
-    /** Permission grant: grant the permission as an install permission. */
-    private static final int GRANT_INSTALL = 2;
-
-    /** Permission grant: grant the permission as a runtime one. */
-    private static final int GRANT_RUNTIME = 3;
-
-    /** Permission grant: grant as runtime a permission that was granted as an install time one. */
-    private static final int GRANT_UPGRADE = 4;
-
     /** Canonical intent used to identify what counts as a "web browser" app */
     private static final Intent sBrowserIntent;
     static {
@@ -753,9 +741,6 @@
 
     PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
 
-    // System configuration read by SystemConfig.
-    final int[] mGlobalGids;
-    final SparseArray<ArraySet<String>> mSystemPermissions;
     @GuardedBy("mAvailableFeatures")
     final ArrayMap<String, FeatureInfo> mAvailableFeatures;
 
@@ -938,10 +923,6 @@
     final ArrayMap<ComponentName, PackageParser.Instrumentation> mInstrumentation =
             new ArrayMap<ComponentName, PackageParser.Instrumentation>();
 
-    // Mapping from permission names to info about them.
-    final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups =
-            new ArrayMap<String, PackageParser.PermissionGroup>();
-
     // Packages whose data we have transfered into another package, thus
     // should no longer exist.
     final ArraySet<String> mTransferedPackages = new ArraySet<String>();
@@ -1016,8 +997,6 @@
 
     private File mCacheDir;
 
-    private ArraySet<String> mPrivappPermissionsViolations;
-
     private Future<?> mPrepareAppDataFuture;
 
     private static class IFVerificationParams {
@@ -1405,8 +1384,6 @@
     final @NonNull String mServicesSystemSharedLibraryPackageName;
     final @NonNull String mSharedSystemSharedLibraryPackageName;
 
-    final boolean mPermissionReviewRequired;
-
     private final PackageUsage mPackageUsage = new PackageUsage();
     private final CompilerStats mCompilerStats = new CompilerStats();
 
@@ -1928,9 +1905,11 @@
             }
         }
         @Override
-        public void onPermissionUpdated(int userId) {
+        public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
             synchronized (mPackages) {
-                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+                for (int userId : updatedUserIds) {
+                    mSettings.writeRuntimePermissionsForUserLPr(userId, sync);
+                }
             }
         }
         @Override
@@ -2363,9 +2342,6 @@
 
         mContext = context;
 
-        mPermissionReviewRequired = context.getResources().getBoolean(
-                R.bool.config_permissionReviewRequired);
-
         mFactoryTest = factoryTest;
         mOnlyCore = onlyCore;
         mMetrics = new DisplayMetrics();
@@ -2434,8 +2410,6 @@
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "get system config");
         SystemConfig systemConfig = SystemConfig.getInstance();
-        mGlobalGids = systemConfig.getGlobalGids();
-        mSystemPermissions = systemConfig.getSystemPermissions();
         mAvailableFeatures = systemConfig.getAvailableFeatures();
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
@@ -2872,13 +2846,14 @@
             // cases get permissions that the user didn't initially explicitly
             // allow...  it would be nice to have some better way to handle
             // this situation.
-            int updateFlags = UPDATE_PERMISSIONS_ALL;
-            if (ver.sdkVersion != mSdkVersion) {
+            final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
+            if (sdkUpdated) {
                 Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to "
                         + mSdkVersion + "; regranting permissions for internal storage");
-                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
+            mPermissionManager.updateAllPermissions(
+                    StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(),
+                    mPermissionCallback);
             ver.sdkVersion = mSdkVersion;
 
             // If this is the first boot or an update from pre-M, and it is a normal
@@ -3597,7 +3572,7 @@
         for (String packageName : packages) {
             PackageParser.Package pkg = mPackages.get(packageName);
             if (pkg != null) {
-                if (!pkg.isSystemApp()) {
+                if (!pkg.isSystem()) {
                     Slog.w(TAG, "Non-system app '" + packageName + "' in sysconfig <app-link>");
                     continue;
                 }
@@ -3744,19 +3719,16 @@
      * <p>
      * Currently, there are three cases in which this can occur:
      * <ol>
-     * <li>The calling application is a "special" process. The special
-     *     processes are {@link Process#SYSTEM_UID}, {@link Process#SHELL_UID}
-     *     and {@code 0}</li>
+     * <li>The calling application is a "special" process. Special processes
+     *     are those with a UID < {@link Process#FIRST_APPLICATION_UID}.</li>
      * <li>The calling application has the permission
-     *     {@link android.Manifest.permission#ACCESS_INSTANT_APPS}</li>
+     *     {@link android.Manifest.permission#ACCESS_INSTANT_APPS}.</li>
      * <li>The calling application is the default launcher on the
      *     system partition.</li>
      * </ol>
      */
     private boolean canViewInstantApps(int callingUid, int userId) {
-        if (callingUid == Process.SYSTEM_UID
-                || callingUid == Process.SHELL_UID
-                || callingUid == Process.ROOT_UID) {
+        if (callingUid < Process.FIRST_APPLICATION_UID) {
             return true;
         }
         if (mContext.checkCallingOrSelfPermission(
@@ -4228,44 +4200,22 @@
     @Override
     public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName,
             int flags) {
-        // TODO Move this to PermissionManager when mPermissionGroups is moved there
-        synchronized (mPackages) {
-            if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
-                // This is thrown as NameNotFoundException
-                return null;
-            }
-        }
-        return new ParceledListSlice<>(
-                mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid()));
+        final List<PermissionInfo> permissionList =
+                mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid());
+        return (permissionList == null) ? null : new ParceledListSlice<>(permissionList);
     }
 
     @Override
-    public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return null;
-        }
-        // reader
-        synchronized (mPackages) {
-            return PackageParser.generatePermissionGroupInfo(
-                    mPermissionGroups.get(name), flags);
-        }
+    public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) {
+        return mPermissionManager.getPermissionGroupInfo(groupName, flags, getCallingUid());
     }
 
     @Override
     public @NonNull ParceledListSlice<PermissionGroupInfo> getAllPermissionGroups(int flags) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return ParceledListSlice.emptyList();
-        }
-        // reader
-        synchronized (mPackages) {
-            final int N = mPermissionGroups.size();
-            ArrayList<PermissionGroupInfo> out
-                    = new ArrayList<PermissionGroupInfo>(N);
-            for (PackageParser.PermissionGroup pg : mPermissionGroups.values()) {
-                out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
-            }
-            return new ParceledListSlice<>(out);
-        }
+        final List<PermissionGroupInfo> permissionList =
+                mPermissionManager.getAllPermissionGroups(flags, getCallingUid());
+        return (permissionList == null)
+                ? ParceledListSlice.emptyList() : new ParceledListSlice<>(permissionList);
     }
 
     private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
@@ -5138,59 +5088,7 @@
 
     @Override
     public int checkUidPermission(String permName, int uid) {
-        final int callingUid = Binder.getCallingUid();
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
-        final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
-        final int userId = UserHandle.getUserId(uid);
-        if (!sUserManager.exists(userId)) {
-            return PackageManager.PERMISSION_DENIED;
-        }
-
-        synchronized (mPackages) {
-            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
-            if (obj != null) {
-                if (obj instanceof SharedUserSetting) {
-                    if (isCallerInstantApp) {
-                        return PackageManager.PERMISSION_DENIED;
-                    }
-                } else if (obj instanceof PackageSetting) {
-                    final PackageSetting ps = (PackageSetting) obj;
-                    if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
-                        return PackageManager.PERMISSION_DENIED;
-                    }
-                }
-                final SettingBase settingBase = (SettingBase) obj;
-                final PermissionsState permissionsState = settingBase.getPermissionsState();
-                if (permissionsState.hasPermission(permName, userId)) {
-                    if (isUidInstantApp) {
-                        if (mSettings.mPermissions.isPermissionInstant(permName)) {
-                            return PackageManager.PERMISSION_GRANTED;
-                        }
-                    } else {
-                        return PackageManager.PERMISSION_GRANTED;
-                    }
-                }
-                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
-                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
-                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
-                    return PackageManager.PERMISSION_GRANTED;
-                }
-            } else {
-                ArraySet<String> perms = mSystemPermissions.get(uid);
-                if (perms != null) {
-                    if (perms.contains(permName)) {
-                        return PackageManager.PERMISSION_GRANTED;
-                    }
-                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
-                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
-                        return PackageManager.PERMISSION_GRANTED;
-                    }
-                }
-            }
-        }
-
-        return PackageManager.PERMISSION_DENIED;
+        return mPermissionManager.checkUidPermission(permName, uid, getCallingUid());
     }
 
     @Override
@@ -5352,7 +5250,9 @@
         }
 
         synchronized (mPackages) {
-            updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL);
+            mPermissionManager.updateAllPermissions(
+                    StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
+                    mPermissionCallback);
             for (int userId : UserManagerService.getInstance().getUserIds()) {
                 final int packageCount = mPackages.size();
                 for (int i = 0; i < packageCount; i++) {
@@ -5369,7 +5269,8 @@
 
     @Override
     public int getPermissionFlags(String permName, String packageName, int userId) {
-        return mPermissionManager.getPermissionFlags(permName, packageName, getCallingUid(), userId);
+        return mPermissionManager.getPermissionFlags(
+                permName, packageName, getCallingUid(), userId);
     }
 
     @Override
@@ -9914,7 +9815,7 @@
                 // it is better for the user to reinstall than to be in an limbo
                 // state. Also libs disappearing under an app should never happen
                 // - just in case.
-                if (!pkg.isSystemApp() || pkg.isUpdatedSystemApp()) {
+                if (!pkg.isSystem() || pkg.isUpdatedSystemApp()) {
                     final int flags = pkg.isUpdatedSystemApp()
                             ? PackageManager.DELETE_KEEP_DATA : 0;
                     deletePackageLIF(pkg.packageName, null, true, sUserManager.getUserIds(),
@@ -10063,7 +9964,7 @@
         assertPackageIsValid(pkg, policyFlags, scanFlags);
 
         if (Build.IS_DEBUGGABLE &&
-                pkg.isPrivilegedApp() &&
+                pkg.isPrivileged() &&
                 !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
             PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
         }
@@ -11156,54 +11057,15 @@
                 if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Activities: " + r);
             }
 
-            N = pkg.permissionGroups.size();
-            r = null;
-            for (i=0; i<N; i++) {
-                PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
-                PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
-                final String curPackageName = cur == null ? null : cur.info.packageName;
-                // Dont allow ephemeral apps to define new permission groups.
-                if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
-                    Slog.w(TAG, "Permission group " + pg.info.name + " from package "
-                            + pg.info.packageName
-                            + " ignored: instant apps cannot define new permission groups.");
-                    continue;
-                }
-                final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
-                if (cur == null || isPackageUpdate) {
-                    mPermissionGroups.put(pg.info.name, pg);
-                    if (chatty) {
-                        if (r == null) {
-                            r = new StringBuilder(256);
-                        } else {
-                            r.append(' ');
-                        }
-                        if (isPackageUpdate) {
-                            r.append("UPD:");
-                        }
-                        r.append(pg.info.name);
-                    }
-                } else {
-                    Slog.w(TAG, "Permission group " + pg.info.name + " from package "
-                            + pg.info.packageName + " ignored: original from "
-                            + cur.info.packageName);
-                    if (chatty) {
-                        if (r == null) {
-                            r = new StringBuilder(256);
-                        } else {
-                            r.append(' ');
-                        }
-                        r.append("DUP:");
-                        r.append(pg.info.name);
-                    }
-                }
-            }
-            if (r != null) {
-                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permission Groups: " + r);
+            // Don't allow ephemeral applications to define new permissions groups.
+            if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+                Slog.w(TAG, "Permission groups from package " + pkg.packageName
+                        + " ignored: instant apps cannot define new permission groups.");
+            } else {
+                mPermissionManager.addAllPermissionGroups(pkg, chatty);
             }
 
-
-            // Dont allow ephemeral apps to define new permissions.
+            // Don't allow ephemeral applications to define new permissions.
             if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
                 Slog.w(TAG, "Permissions from package " + pkg.packageName
                         + " ignored: instant apps cannot define new permissions.");
@@ -11995,611 +11857,6 @@
         }
     }
 
-    public static final int UPDATE_PERMISSIONS_ALL = 1<<0;
-    public static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
-    public static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
-
-    private void updatePermissionsLPw(PackageParser.Package pkg, int flags) {
-        // Update the parent permissions
-        updatePermissionsLPw(pkg.packageName, pkg, flags);
-        // Update the child permissions
-        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
-        for (int i = 0; i < childCount; i++) {
-            PackageParser.Package childPkg = pkg.childPackages.get(i);
-            updatePermissionsLPw(childPkg.packageName, childPkg, flags);
-        }
-    }
-
-    private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
-            int flags) {
-        final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null;
-        updatePermissionsLocked(changingPkg, pkgInfo, volumeUuid, flags);
-    }
-
-    private void updatePermissionsLocked(String changingPkg,
-            PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
-        // TODO: Most of the methods exposing BasePermission internals [source package name,
-        // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
-        // have package settings, we should make note of it elsewhere [map between
-        // source package name and BasePermission] and cycle through that here. Then we
-        // define a single method on BasePermission that takes a PackageSetting, changing
-        // package name and a package.
-        // NOTE: With this approach, we also don't need to tree trees differently than
-        // normal permissions. Today, we need two separate loops because these BasePermission
-        // objects are stored separately.
-        // Make sure there are no dangling permission trees.
-        flags = mPermissionManager.updatePermissionTrees(changingPkg, pkgInfo, flags);
-
-        // Make sure all dynamic permissions have been assigned to a package,
-        // and make sure there are no dangling permissions.
-        flags = mPermissionManager.updatePermissions(changingPkg, pkgInfo, flags);
-
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
-        // Now update the permissions for all packages, in particular
-        // replace the granted permissions of the system packages.
-        if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
-            for (PackageParser.Package pkg : mPackages.values()) {
-                if (pkg != pkgInfo) {
-                    // Only replace for packages on requested volume
-                    final String volumeUuid = getVolumeUuidForPackage(pkg);
-                    final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
-                            && Objects.equals(replaceVolumeUuid, volumeUuid);
-                    grantPermissionsLPw(pkg, replace, changingPkg);
-                }
-            }
-        }
-
-        if (pkgInfo != null) {
-            // Only replace for packages on requested volume
-            final String volumeUuid = getVolumeUuidForPackage(pkgInfo);
-            final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
-                    && Objects.equals(replaceVolumeUuid, volumeUuid);
-            grantPermissionsLPw(pkgInfo, replace, changingPkg);
-        }
-        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-    }
-
-    private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
-            String packageOfInterest) {
-        // IMPORTANT: There are two types of permissions: install and runtime.
-        // Install time permissions are granted when the app is installed to
-        // all device users and users added in the future. Runtime permissions
-        // are granted at runtime explicitly to specific users. Normal and signature
-        // protected permissions are install time permissions. Dangerous permissions
-        // are install permissions if the app's target SDK is Lollipop MR1 or older,
-        // otherwise they are runtime permissions. This function does not manage
-        // runtime permissions except for the case an app targeting Lollipop MR1
-        // being upgraded to target a newer SDK, in which case dangerous permissions
-        // are transformed from install time to runtime ones.
-
-        final PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
-            return;
-        }
-
-        PermissionsState permissionsState = ps.getPermissionsState();
-        PermissionsState origPermissions = permissionsState;
-
-        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
-
-        boolean runtimePermissionsRevoked = false;
-        int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY;
-
-        boolean changedInstallPermission = false;
-
-        if (replace) {
-            ps.installPermissionsFixed = false;
-            if (!ps.isSharedUser()) {
-                origPermissions = new PermissionsState(permissionsState);
-                permissionsState.reset();
-            } else {
-                // We need to know only about runtime permission changes since the
-                // calling code always writes the install permissions state but
-                // the runtime ones are written only if changed. The only cases of
-                // changed runtime permissions here are promotion of an install to
-                // runtime and revocation of a runtime from a shared user.
-                changedRuntimePermissionUserIds =
-                        mPermissionManager.revokeUnusedSharedUserPermissions(
-                                ps.sharedUser, UserManagerService.getInstance().getUserIds());
-                if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) {
-                    runtimePermissionsRevoked = true;
-                }
-            }
-        }
-
-        permissionsState.setGlobalGids(mGlobalGids);
-
-        final int N = pkg.requestedPermissions.size();
-        for (int i=0; i<N; i++) {
-            final String name = pkg.requestedPermissions.get(i);
-            final BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(name);
-            final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
-                    >= Build.VERSION_CODES.M;
-
-            if (DEBUG_INSTALL) {
-                Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
-            }
-
-            if (bp == null || bp.getSourcePackageSetting() == null) {
-                if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
-                    if (DEBUG_PERMISSIONS) {
-                        Slog.i(TAG, "Unknown permission " + name
-                                + " in package " + pkg.packageName);
-                    }
-                }
-                continue;
-            }
-
-
-            // Limit ephemeral apps to ephemeral allowed permissions.
-            if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
-                if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + " for package "
-                            + pkg.packageName);
-                }
-                continue;
-            }
-
-            if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
-                if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying runtime-only permission " + bp.getName() + " for package "
-                            + pkg.packageName);
-                }
-                continue;
-            }
-
-            final String perm = bp.getName();
-            boolean allowedSig = false;
-            int grant = GRANT_DENIED;
-
-            // Keep track of app op permissions.
-            if (bp.isAppOp()) {
-                mSettings.addAppOpPackage(perm, pkg.packageName);
-            }
-
-            if (bp.isNormal()) {
-                // For all apps normal permissions are install time ones.
-                grant = GRANT_INSTALL;
-            } else if (bp.isRuntime()) {
-                // If a permission review is required for legacy apps we represent
-                // their permissions as always granted runtime ones since we need
-                // to keep the review required permission flag per user while an
-                // install permission's state is shared across all users.
-                if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
-                    // For legacy apps dangerous permissions are install time ones.
-                    grant = GRANT_INSTALL;
-                } else if (origPermissions.hasInstallPermission(bp.getName())) {
-                    // For legacy apps that became modern, install becomes runtime.
-                    grant = GRANT_UPGRADE;
-                } else if (mPromoteSystemApps
-                        && isSystemApp(ps)
-                        && mExistingSystemPackages.contains(ps.name)) {
-                    // For legacy system apps, install becomes runtime.
-                    // We cannot check hasInstallPermission() for system apps since those
-                    // permissions were granted implicitly and not persisted pre-M.
-                    grant = GRANT_UPGRADE;
-                } else {
-                    // For modern apps keep runtime permissions unchanged.
-                    grant = GRANT_RUNTIME;
-                }
-            } else if (bp.isSignature()) {
-                // For all apps signature permissions are install time ones.
-                allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
-                if (allowedSig) {
-                    grant = GRANT_INSTALL;
-                }
-            }
-
-            if (DEBUG_PERMISSIONS) {
-                Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName);
-            }
-
-            if (grant != GRANT_DENIED) {
-                if (!isSystemApp(ps) && ps.installPermissionsFixed) {
-                    // If this is an existing, non-system package, then
-                    // we can't add any new permissions to it.
-                    if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
-                        // Except...  if this is a permission that was added
-                        // to the platform (note: need to only do this when
-                        // updating the platform).
-                        if (!isNewPlatformPermissionForPackage(perm, pkg)) {
-                            grant = GRANT_DENIED;
-                        }
-                    }
-                }
-
-                switch (grant) {
-                    case GRANT_INSTALL: {
-                        // Revoke this as runtime permission to handle the case of
-                        // a runtime permission being downgraded to an install one.
-                        // Also in permission review mode we keep dangerous permissions
-                        // for legacy apps
-                        for (int userId : UserManagerService.getInstance().getUserIds()) {
-                            if (origPermissions.getRuntimePermissionState(
-                                    perm, userId) != null) {
-                                // Revoke the runtime permission and clear the flags.
-                                origPermissions.revokeRuntimePermission(bp, userId);
-                                origPermissions.updatePermissionFlags(bp, userId,
-                                      PackageManager.MASK_PERMISSION_FLAGS, 0);
-                                // If we revoked a permission permission, we have to write.
-                                changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                        changedRuntimePermissionUserIds, userId);
-                            }
-                        }
-                        // Grant an install permission.
-                        if (permissionsState.grantInstallPermission(bp) !=
-                                PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                            changedInstallPermission = true;
-                        }
-                    } break;
-
-                    case GRANT_RUNTIME: {
-                        // Grant previously granted runtime permissions.
-                        for (int userId : UserManagerService.getInstance().getUserIds()) {
-                            PermissionState permissionState = origPermissions
-                                    .getRuntimePermissionState(perm, userId);
-                            int flags = permissionState != null
-                                    ? permissionState.getFlags() : 0;
-                            if (origPermissions.hasRuntimePermission(perm, userId)) {
-                                // Don't propagate the permission in a permission review mode if
-                                // the former was revoked, i.e. marked to not propagate on upgrade.
-                                // Note that in a permission review mode install permissions are
-                                // represented as constantly granted runtime ones since we need to
-                                // keep a per user state associated with the permission. Also the
-                                // revoke on upgrade flag is no longer applicable and is reset.
-                                final boolean revokeOnUpgrade = (flags & PackageManager
-                                        .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
-                                if (revokeOnUpgrade) {
-                                    flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
-                                    // Since we changed the flags, we have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                                if (!mPermissionReviewRequired || !revokeOnUpgrade) {
-                                    if (permissionsState.grantRuntimePermission(bp, userId) ==
-                                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                        // If we cannot put the permission as it was,
-                                        // we have to write.
-                                        changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                                changedRuntimePermissionUserIds, userId);
-                                    }
-                                }
-
-                                // If the app supports runtime permissions no need for a review.
-                                if (mPermissionReviewRequired
-                                        && appSupportsRuntimePermissions
-                                        && (flags & PackageManager
-                                                .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                                    flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-                                    // Since we changed the flags, we have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                            } else if (mPermissionReviewRequired
-                                    && !appSupportsRuntimePermissions) {
-                                // For legacy apps that need a permission review, every new
-                                // runtime permission is granted but it is pending a review.
-                                // We also need to review only platform defined runtime
-                                // permissions as these are the only ones the platform knows
-                                // how to disable the API to simulate revocation as legacy
-                                // apps don't expect to run with revoked permissions.
-                                if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
-                                    if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
-                                        flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
-                                        // We changed the flags, hence have to write.
-                                        changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                                changedRuntimePermissionUserIds, userId);
-                                    }
-                                }
-                                if (permissionsState.grantRuntimePermission(bp, userId)
-                                        != PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                    // We changed the permission, hence have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                            }
-                            // Propagate the permission flags.
-                            permissionsState.updatePermissionFlags(bp, userId, flags, flags);
-                        }
-                    } break;
-
-                    case GRANT_UPGRADE: {
-                        // Grant runtime permissions for a previously held install permission.
-                        PermissionState permissionState = origPermissions
-                                .getInstallPermissionState(perm);
-                        final int flags = permissionState != null ? permissionState.getFlags() : 0;
-
-                        if (origPermissions.revokeInstallPermission(bp)
-                                != PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                            // We will be transferring the permission flags, so clear them.
-                            origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                                    PackageManager.MASK_PERMISSION_FLAGS, 0);
-                            changedInstallPermission = true;
-                        }
-
-                        // If the permission is not to be promoted to runtime we ignore it and
-                        // also its other flags as they are not applicable to install permissions.
-                        if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {
-                            for (int userId : currentUserIds) {
-                                if (permissionsState.grantRuntimePermission(bp, userId) !=
-                                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                    // Transfer the permission flags.
-                                    permissionsState.updatePermissionFlags(bp, userId,
-                                            flags, flags);
-                                    // If we granted the permission, we have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                            }
-                        }
-                    } break;
-
-                    default: {
-                        if (packageOfInterest == null
-                                || packageOfInterest.equals(pkg.packageName)) {
-                            if (DEBUG_PERMISSIONS) {
-                                Slog.i(TAG, "Not granting permission " + perm
-                                        + " to package " + pkg.packageName
-                                        + " because it was previously installed without");
-                            }
-                        }
-                    } break;
-                }
-            } else {
-                if (permissionsState.revokeInstallPermission(bp) !=
-                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                    // Also drop the permission flags.
-                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                            PackageManager.MASK_PERMISSION_FLAGS, 0);
-                    changedInstallPermission = true;
-                    Slog.i(TAG, "Un-granting permission " + perm
-                            + " from package " + pkg.packageName
-                            + " (protectionLevel=" + bp.getProtectionLevel()
-                            + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
-                            + ")");
-                } else if (bp.isAppOp()) {
-                    // Don't print warning for app op permissions, since it is fine for them
-                    // not to be granted, there is a UI for the user to decide.
-                    if (DEBUG_PERMISSIONS
-                            && (packageOfInterest == null
-                                    || packageOfInterest.equals(pkg.packageName))) {
-                        Slog.i(TAG, "Not granting permission " + perm
-                                + " to package " + pkg.packageName
-                                + " (protectionLevel=" + bp.getProtectionLevel()
-                                + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
-                                + ")");
-                    }
-                }
-            }
-        }
-
-        if ((changedInstallPermission || replace) && !ps.installPermissionsFixed &&
-                !isSystemApp(ps) || isUpdatedSystemApp(ps)){
-            // This is the first that we have heard about this package, so the
-            // permissions we have now selected are fixed until explicitly
-            // changed.
-            ps.installPermissionsFixed = true;
-        }
-
-        // Persist the runtime permissions state for users with changes. If permissions
-        // were revoked because no app in the shared user declares them we have to
-        // write synchronously to avoid losing runtime permissions state.
-        for (int userId : changedRuntimePermissionUserIds) {
-            mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked);
-        }
-    }
-
-    private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
-        boolean allowed = false;
-        final int NP = PackageParser.NEW_PERMISSIONS.length;
-        for (int ip=0; ip<NP; ip++) {
-            final PackageParser.NewPermissionInfo npi
-                    = PackageParser.NEW_PERMISSIONS[ip];
-            if (npi.name.equals(perm)
-                    && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
-                allowed = true;
-                Log.i(TAG, "Auto-granting " + perm + " to old pkg "
-                        + pkg.packageName);
-                break;
-            }
-        }
-        return allowed;
-    }
-
-    /**
-     * Determines whether a package is whitelisted for a particular privapp permission.
-     *
-     * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted.
-     *
-     * <p>This handles parent/child apps.
-     */
-    private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) {
-        ArraySet<String> wlPermissions = SystemConfig.getInstance()
-                .getPrivAppPermissions(pkg.packageName);
-        // Let's check if this package is whitelisted...
-        boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm);
-        // If it's not, we'll also tail-recurse to the parent.
-        return whitelisted ||
-                pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage);
-    }
-
-    private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
-            BasePermission bp, PermissionsState origPermissions) {
-        boolean oemPermission = bp.isOEM();
-        boolean privilegedPermission = bp.isPrivileged();
-        boolean privappPermissionsDisable =
-                RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
-        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
-        boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
-        if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivilegedApp()
-                && !platformPackage && platformPermission) {
-            if (!hasPrivappWhitelistEntry(perm, pkg)) {
-                Slog.w(TAG, "Privileged permission " + perm + " for package "
-                        + pkg.packageName + " - not in privapp-permissions whitelist");
-                // Only report violations for apps on system image
-                if (!mSystemReady && !pkg.isUpdatedSystemApp()) {
-                    // it's only a reportable violation if the permission isn't explicitly denied
-                    final ArraySet<String> deniedPermissions = SystemConfig.getInstance()
-                            .getPrivAppDenyPermissions(pkg.packageName);
-                    final boolean permissionViolation =
-                            deniedPermissions == null || !deniedPermissions.contains(perm);
-                    if (permissionViolation
-                            && RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                        if (mPrivappPermissionsViolations == null) {
-                            mPrivappPermissionsViolations = new ArraySet<>();
-                        }
-                        mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm);
-                    } else {
-                        return false;
-                    }
-                }
-                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    return false;
-                }
-            }
-        }
-        boolean allowed = (compareSignatures(
-                bp.getSourcePackageSetting().signatures.mSignatures, pkg.mSignatures)
-                        == PackageManager.SIGNATURE_MATCH)
-                || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
-                        == PackageManager.SIGNATURE_MATCH);
-        if (!allowed && (privilegedPermission || oemPermission)) {
-            if (isSystemApp(pkg)) {
-                // For updated system applications, a privileged/oem permission
-                // is granted only if it had been defined by the original application.
-                if (pkg.isUpdatedSystemApp()) {
-                    final PackageSetting sysPs = mSettings
-                            .getDisabledSystemPkgLPr(pkg.packageName);
-                    if (sysPs != null && sysPs.getPermissionsState().hasInstallPermission(perm)) {
-                        // If the original was granted this permission, we take
-                        // that grant decision as read and propagate it to the
-                        // update.
-                        if ((privilegedPermission && sysPs.isPrivileged())
-                                || (oemPermission && sysPs.isOem()
-                                        && canGrantOemPermission(sysPs, perm))) {
-                            allowed = true;
-                        }
-                    } else {
-                        // The system apk may have been updated with an older
-                        // version of the one on the data partition, but which
-                        // granted a new system permission that it didn't have
-                        // before.  In this case we do want to allow the app to
-                        // now get the new permission if the ancestral apk is
-                        // privileged to get it.
-                        if (sysPs != null && sysPs.pkg != null
-                                && isPackageRequestingPermission(sysPs.pkg, perm)
-                                && ((privilegedPermission && sysPs.isPrivileged())
-                                        || (oemPermission && sysPs.isOem()
-                                                && canGrantOemPermission(sysPs, perm)))) {
-                            allowed = true;
-                        }
-                        // Also if a privileged parent package on the system image or any of
-                        // its children requested a privileged/oem permission, the updated child
-                        // packages can also get the permission.
-                        if (pkg.parentPackage != null) {
-                            final PackageSetting disabledSysParentPs = mSettings
-                                    .getDisabledSystemPkgLPr(pkg.parentPackage.packageName);
-                            final PackageParser.Package disabledSysParentPkg =
-                                    (disabledSysParentPs == null || disabledSysParentPs.pkg == null)
-                                    ? null : disabledSysParentPs.pkg;
-                            if (disabledSysParentPkg != null
-                                    && ((privilegedPermission && disabledSysParentPs.isPrivileged())
-                                            || (oemPermission && disabledSysParentPs.isOem()))) {
-                                if (isPackageRequestingPermission(disabledSysParentPkg, perm)
-                                        && canGrantOemPermission(disabledSysParentPs, perm)) {
-                                    allowed = true;
-                                } else if (disabledSysParentPkg.childPackages != null) {
-                                    final int count = disabledSysParentPkg.childPackages.size();
-                                    for (int i = 0; i < count; i++) {
-                                        final PackageParser.Package disabledSysChildPkg =
-                                                disabledSysParentPkg.childPackages.get(i);
-                                        final PackageSetting disabledSysChildPs =
-                                                mSettings.getDisabledSystemPkgLPr(
-                                                        disabledSysChildPkg.packageName);
-                                        if (isPackageRequestingPermission(disabledSysChildPkg, perm)
-                                                && canGrantOemPermission(
-                                                        disabledSysChildPs, perm)) {
-                                            allowed = true;
-                                            break;
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                } else {
-                    allowed = (privilegedPermission && isPrivilegedApp(pkg))
-                            || (oemPermission && isOemApp(pkg)
-                                    && canGrantOemPermission(
-                                            mSettings.getPackageLPr(pkg.packageName), perm));
-                }
-            }
-        }
-        if (!allowed) {
-            if (!allowed
-                    && bp.isPre23()
-                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
-                // If this was a previously normal/dangerous permission that got moved
-                // to a system permission as part of the runtime permission redesign, then
-                // we still want to blindly grant it to old apps.
-                allowed = true;
-            }
-            if (!allowed && bp.isInstaller()
-                    && pkg.packageName.equals(mRequiredInstallerPackage)) {
-                // If this permission is to be granted to the system installer and
-                // this app is an installer, then it gets the permission.
-                allowed = true;
-            }
-            if (!allowed && bp.isVerifier()
-                    && pkg.packageName.equals(mRequiredVerifierPackage)) {
-                // If this permission is to be granted to the system verifier and
-                // this app is a verifier, then it gets the permission.
-                allowed = true;
-            }
-            if (!allowed && bp.isPreInstalled()
-                    && isSystemApp(pkg)) {
-                // Any pre-installed system app is allowed to get this permission.
-                allowed = true;
-            }
-            if (!allowed && bp.isDevelopment()) {
-                // For development permissions, a development permission
-                // is granted only if it was already granted.
-                allowed = origPermissions.hasInstallPermission(perm);
-            }
-            if (!allowed && bp.isSetup()
-                    && pkg.packageName.equals(mSetupWizardPackage)) {
-                // If this permission is to be granted to the system setup wizard and
-                // this app is a setup wizard, then it gets the permission.
-                allowed = true;
-            }
-        }
-        return allowed;
-    }
-
-    private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
-        if (!ps.isOem()) {
-            return false;
-        }
-        // all oem permissions must explicitly be granted or denied
-        final Boolean granted =
-                SystemConfig.getInstance().getOemPermissions(ps.name).get(permission);
-        if (granted == null) {
-            throw new IllegalStateException("OEM permission" + permission + " requested by package "
-                    + ps.name + " must be explicitly declared granted or not");
-        }
-        return Boolean.TRUE == granted;
-    }
-
-    private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) {
-        final int permCount = pkg.requestedPermissions.size();
-        for (int j = 0; j < permCount; j++) {
-            String requestedPermission = pkg.requestedPermissions.get(j);
-            if (permission.equals(requestedPermission)) {
-                return true;
-            }
-        }
-        return false;
-    }
 
     final class ActivityIntentResolver
             extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
@@ -16491,7 +15748,7 @@
             }
 
             // don't allow a system upgrade unless the upgrade hash matches
-            if (oldPackage.restrictUpdateHash != null && oldPackage.isSystemApp()) {
+            if (oldPackage.restrictUpdateHash != null && oldPackage.isSystem()) {
                 byte[] digestBytes = null;
                 try {
                     final MessageDigest digest = MessageDigest.getInstance("SHA-512");
@@ -16758,7 +16015,9 @@
                     setInstallerPackageNameLPw(deletedPackage, installerPackageName);
 
                     // Update permissions for restored package
-                    updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL);
+                    mPermissionManager.updatePermissions(
+                            deletedPackage.packageName, deletedPackage, false, mPackages.values(),
+                            mPermissionCallback);
 
                     mSettings.writeLPr();
                 }
@@ -16900,7 +16159,9 @@
                 setInstallerPackageNameLPw(deletedPackage, installerPackageName);
 
                 // Update permissions for restored package
-                updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL);
+                mPermissionManager.updatePermissions(
+                        deletedPackage.packageName, deletedPackage, false, mPackages.values(),
+                        mPermissionCallback);
 
                 mSettings.writeLPr();
             }
@@ -17013,12 +16274,12 @@
         }
     }
 
-    private void updateSettingsInternalLI(PackageParser.Package newPackage,
+    private void updateSettingsInternalLI(PackageParser.Package pkg,
             String installerPackageName, int[] allUsers, int[] installedForUsers,
             PackageInstalledInfo res, UserHandle user, int installReason) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
-        String pkgName = newPackage.packageName;
+        String pkgName = pkg.packageName;
         synchronized (mPackages) {
             //write settings. the installStatus will be incomplete at this stage.
             //note that the new package setting would have already been
@@ -17030,18 +16291,18 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
-        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.codePath);
+        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath);
         synchronized (mPackages) {
-            updatePermissionsLPw(newPackage.packageName, newPackage,
-                    UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
-                            ? UPDATE_PERMISSIONS_ALL : 0));
+// NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions
+            mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(),
+                    mPermissionCallback);
             // For system-bundled packages, we assume that installing an upgraded version
             // of the package implies that the user actually wants to run that new code,
             // so we enable the package.
             PackageSetting ps = mSettings.mPackages.get(pkgName);
             final int userId = user.getIdentifier();
             if (ps != null) {
-                if (isSystemApp(newPackage)) {
+                if (isSystemApp(pkg)) {
                     if (DEBUG_INSTALL) {
                         Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
                     }
@@ -17101,8 +16362,8 @@
                 mSettings.writeKernelMappingLPr(ps);
             }
             res.name = pkgName;
-            res.uid = newPackage.applicationInfo.uid;
-            res.pkg = newPackage;
+            res.uid = pkg.applicationInfo.uid;
+            res.pkg = pkg;
             mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE);
             mSettings.setInstallerPackageName(pkgName, installerPackageName);
             res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
@@ -17801,18 +17062,6 @@
         return installFlags;
     }
 
-    private String getVolumeUuidForPackage(PackageParser.Package pkg) {
-        if (isExternal(pkg)) {
-            if (TextUtils.isEmpty(pkg.volumeUuid)) {
-                return StorageManager.UUID_PRIMARY_PHYSICAL;
-            } else {
-                return pkg.volumeUuid;
-            }
-        } else {
-            return StorageManager.UUID_PRIVATE_INTERNAL;
-        }
-    }
-
     private VersionInfo getSettingsVersionForPackage(PackageParser.Package pkg) {
         if (isExternal(pkg)) {
             if (TextUtils.isEmpty(pkg.volumeUuid)) {
@@ -18457,7 +17706,8 @@
                     if (outInfo != null) {
                         outInfo.removedAppId = removedAppId;
                     }
-                    updatePermissionsLPw(deletedPs.name, null, 0);
+                    mPermissionManager.updatePermissions(
+                            deletedPs.name, null, false, mPackages.values(), mPermissionCallback);
                     if (deletedPs.sharedUser != null) {
                         // Remove permissions associated with package. Since runtime
                         // permissions are per user we have to kill the removed package
@@ -18660,21 +17910,21 @@
             parseFlags |= PackageParser.PARSE_IS_OEM;
         }
 
-        final PackageParser.Package newPkg =
+        final PackageParser.Package pkg =
                 scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null);
 
         try {
             // update shared libraries for the newly re-installed system package
-            updateSharedLibrariesLPr(newPkg, null);
+            updateSharedLibrariesLPr(pkg, null);
         } catch (PackageManagerException e) {
             Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
         }
 
-        prepareAppDataAfterInstallLIF(newPkg);
+        prepareAppDataAfterInstallLIF(pkg);
 
         // writer
         synchronized (mPackages) {
-            PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
+            PackageSetting ps = mSettings.mPackages.get(pkg.packageName);
 
             // Propagate the permissions state as we do not want to drop on the floor
             // runtime permissions. The update permissions method below will take
@@ -18682,8 +17932,8 @@
             if (origPermissionState != null) {
                 ps.getPermissionsState().copyFrom(origPermissionState);
             }
-            updatePermissionsLPw(newPkg.packageName, newPkg,
-                    UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+            mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(),
+                    mPermissionCallback);
 
             final boolean applyUserRestrictions
                     = (allUserHandles != null) && (origUserHandles != null);
@@ -18716,7 +17966,7 @@
                 mSettings.writeLPr();
             }
         }
-        return newPkg;
+        return pkg;
     }
 
     private boolean deleteInstalledPackageLIF(PackageSetting ps,
@@ -19333,7 +18583,7 @@
             // If permission review is enabled and this is a legacy app, mark the
             // permission as requiring a review as this is the initial state.
             int flags = 0;
-            if (mPermissionReviewRequired
+            if (mSettings.mPermissions.mPermissionReviewRequired
                     && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                 flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
             }
@@ -20692,7 +19942,7 @@
             // data partition and then replace the version on the system partition.
             final PackageParser.Package deletedPkg = pkgSetting.pkg;
             final boolean isSystemStub = deletedPkg.isStub
-                    && deletedPkg.isSystemApp();
+                    && deletedPkg.isSystem();
             if (isSystemStub
                     && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                             || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
@@ -20732,22 +19982,23 @@
                     synchronized (mPackages) {
                         disableSystemPackageLPw(deletedPkg, tmpPkg);
                     }
-                    final PackageParser.Package newPkg;
+                    final PackageParser.Package pkg;
                     try (PackageFreezer freezer =
                             freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
                         final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                                 | PackageParser.PARSE_ENFORCE_CODE;
-                        newPkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
+                        pkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
                                 0 /*currentTime*/, null /*user*/);
-                        prepareAppDataAfterInstallLIF(newPkg);
+                        prepareAppDataAfterInstallLIF(pkg);
                         synchronized (mPackages) {
                             try {
-                                updateSharedLibrariesLPr(newPkg, null);
+                                updateSharedLibrariesLPr(pkg, null);
                             } catch (PackageManagerException e) {
                                 Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e);
                             }
-                            updatePermissionsLPw(newPkg.packageName, newPkg,
-                                    UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+                            mPermissionManager.updatePermissions(
+                                    pkg.packageName, pkg, true, mPackages.values(),
+                                    mPermissionCallback);
                             mSettings.writeLPr();
                         }
                     } catch (PackageManagerException e) {
@@ -20787,11 +20038,11 @@
                         }
                         return;
                     }
-                    clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
+                    clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
                             | FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-                    clearAppProfilesLIF(newPkg, UserHandle.USER_ALL);
-                    mDexManager.notifyPackageUpdated(newPkg.packageName,
-                            newPkg.baseCodePath, newPkg.splitCodePaths);
+                    clearAppProfilesLIF(pkg, UserHandle.USER_ALL);
+                    mDexManager.notifyPackageUpdated(pkg.packageName,
+                            pkg.baseCodePath, pkg.splitCodePaths);
                 }
             }
             if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
@@ -21102,8 +20353,9 @@
         // permissions, ensure permissions are updated. Beware of dragons if you
         // try optimizing this.
         synchronized (mPackages) {
-            updatePermissionsLocked(null, null, StorageManager.UUID_PRIVATE_INTERNAL,
-                    UPDATE_PERMISSIONS_ALL);
+            mPermissionManager.updateAllPermissions(
+                    StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
+                    mPermissionCallback);
         }
 
         // Kick off any messages waiting for system ready
@@ -21152,10 +20404,7 @@
         sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
         reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);
 
-        if (mPrivappPermissionsViolations != null) {
-            throw new IllegalStateException("Signature|privileged permissions not in "
-                    + "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
-        }
+        mPermissionManager.systemReady();
     }
 
     public void waitForAppDataPrepared() {
@@ -22140,13 +21389,13 @@
         }
 
         synchronized (mPackages) {
-            int updateFlags = UPDATE_PERMISSIONS_ALL;
-            if (ver.sdkVersion != mSdkVersion) {
+            final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
+            if (sdkUpdated) {
                 logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
                         + mSdkVersion + "; regranting permissions for " + volumeUuid);
-                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLocked(null, null, volumeUuid, updateFlags);
+            mPermissionManager.updateAllPermissions(volumeUuid, sdkUpdated, mPackages.values(),
+                    mPermissionCallback);
 
             // Yay, everything is now upgraded
             ver.forceCurrent();
@@ -22594,7 +21843,7 @@
      * requested by the app.
      */
     private boolean maybeMigrateAppDataLIF(PackageParser.Package pkg, int userId) {
-        if (pkg.isSystemApp() && !StorageManager.isFileEncryptedNativeOrEmulated()
+        if (pkg.isSystem() && !StorageManager.isFileEncryptedNativeOrEmulated()
                 && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
             final int storageTarget = pkg.applicationInfo.isDefaultToDeviceProtectedStorage()
                     ? StorageManager.FLAG_STORAGE_DE : StorageManager.FLAG_STORAGE_CE;
@@ -23159,9 +22408,11 @@
             // permissions to keep per user flag state whether review is needed.
             // Hence, if a new user is added we have to propagate dangerous
             // permission grants for these legacy apps.
-            if (mPermissionReviewRequired) {
-                updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
-                        | UPDATE_PERMISSIONS_REPLACE_ALL);
+            if (mSettings.mPermissions.mPermissionReviewRequired) {
+// NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
+                mPermissionManager.updateAllPermissions(
+                        StorageManager.UUID_PRIVATE_INTERNAL, true, mPackages.values(),
+                        mPermissionCallback);
             }
         }
     }
@@ -23606,13 +22857,6 @@
         }
 
         @Override
-        public PackageParser.PermissionGroup getPermissionGroupTEMP(String groupName) {
-            synchronized (mPackages) {
-                return mPermissionGroups.get(groupName);
-            }
-        }
-
-        @Override
         public boolean isInstantApp(String packageName, int userId) {
             return PackageManagerService.this.isInstantApp(packageName, userId);
         }
@@ -23750,24 +22994,8 @@
         @Override
         public boolean isPermissionsReviewRequired(String packageName, int userId) {
             synchronized (mPackages) {
-                // If we do not support permission review, done.
-                if (!mPermissionReviewRequired) {
-                    return false;
-                }
-
-                PackageSetting packageSetting = mSettings.mPackages.get(packageName);
-                if (packageSetting == null) {
-                    return false;
-                }
-
-                // Permission review applies only to apps not supporting the new permission model.
-                if (packageSetting.pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
-                    return false;
-                }
-
-                // Legacy apps have the permission and get user consent on launch.
-                PermissionsState permissionsState = packageSetting.getPermissionsState();
-                return permissionsState.isPermissionReviewRequired(userId);
+                return mPermissionManager.isPermissionsReviewRequired(
+                        mPackages.get(packageName), userId);
             }
         }
 
@@ -23921,6 +23149,16 @@
         }
 
         @Override
+        public boolean isLegacySystemApp(Package pkg) {
+            synchronized (mPackages) {
+                final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                return mPromoteSystemApps
+                        && ps.isSystem()
+                        && mExistingSystemPackages.contains(ps.name);
+            }
+        }
+
+        @Override
         public List<PackageInfo> getOverlayPackages(int userId) {
             final ArrayList<PackageInfo> overlayPackages = new ArrayList<PackageInfo>();
             synchronized (mPackages) {
diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java
index 1fea003..a2099e6 100644
--- a/com/android/server/pm/PackageManagerShellCommand.java
+++ b/com/android/server/pm/PackageManagerShellCommand.java
@@ -16,14 +16,17 @@
 
 package com.android.server.pm;
 
+import android.accounts.IAccountManager;
 import android.app.ActivityManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageManager;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
@@ -41,6 +44,7 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
@@ -49,15 +53,23 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IUserManager;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ShellCommand;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
 import android.text.TextUtils;
-import android.util.ArrayMap;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.PrintWriterPrinter;
+
 import com.android.internal.content.PackageHelper;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.SizedInputStream;
 import com.android.server.SystemConfig;
 
@@ -81,6 +93,12 @@
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
 
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+
 class PackageManagerShellCommand extends ShellCommand {
     /** Path for streaming APK content */
     private static final String STDIN_PATH = "-";
@@ -107,6 +125,20 @@
         final PrintWriter pw = getOutPrintWriter();
         try {
             switch(cmd) {
+                case "path":
+                    return runPath();
+                case "dump":
+                    return runDump();
+                case "list":
+                    return runList();
+                case "resolve-activity":
+                    return runResolveActivity();
+                case "query-activities":
+                    return runQueryIntentActivities();
+                case "query-services":
+                    return runQueryIntentServices();
+                case "query-receivers":
+                    return runQueryIntentReceivers();
                 case "install":
                     return runInstall();
                 case "install-abandon":
@@ -122,44 +154,99 @@
                     return runInstallWrite();
                 case "install-existing":
                     return runInstallExisting();
+                case "set-install-location":
+                    return runSetInstallLocation();
+                case "get-install-location":
+                    return runGetInstallLocation();
+                case "move-package":
+                    return runMovePackage();
+                case "move-primary-storage":
+                    return runMovePrimaryStorage();
                 case "compile":
                     return runCompile();
                 case "reconcile-secondary-dex-files":
                     return runreconcileSecondaryDexFiles();
+                case "force-dex-opt":
+                    return runForceDexOpt();
                 case "bg-dexopt-job":
                     return runDexoptJob();
                 case "dump-profiles":
                     return runDumpProfiles();
-                case "list":
-                    return runList();
                 case "uninstall":
                     return runUninstall();
-                case "resolve-activity":
-                    return runResolveActivity();
-                case "query-activities":
-                    return runQueryIntentActivities();
-                case "query-services":
-                    return runQueryIntentServices();
-                case "query-receivers":
-                    return runQueryIntentReceivers();
+                case "clear":
+                    return runClear();
+                case "enable":
+                    return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+                case "disable":
+                    return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+                case "disable-user":
+                    return runSetEnabledSetting(
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
+                case "disable-until-used":
+                    return runSetEnabledSetting(
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+                case "default-state":
+                    return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+                case "hide":
+                    return runSetHiddenSetting(true);
+                case "unhide":
+                    return runSetHiddenSetting(false);
                 case "suspend":
                     return runSuspend(true);
                 case "unsuspend":
                     return runSuspend(false);
-                case "set-home-activity":
-                    return runSetHomeActivity();
+                case "grant":
+                    return runGrantRevokePermission(true);
+                case "revoke":
+                    return runGrantRevokePermission(false);
+                case "reset-permissions":
+                    return runResetPermissions();
+                case "set-permission-enforced":
+                    return runSetPermissionEnforced();
                 case "get-privapp-permissions":
                     return runGetPrivappPermissions();
                 case "get-privapp-deny-permissions":
                     return runGetPrivappDenyPermissions();
                 case "get-oem-permissions":
                     return runGetOemPermissions();
+                case "set-app-link":
+                    return runSetAppLink();
+                case "get-app-link":
+                    return runGetAppLink();
+                case "trim-caches":
+                    return runTrimCaches();
+                case "create-user":
+                    return runCreateUser();
+                case "remove-user":
+                    return runRemoveUser();
+                case "set-user-restriction":
+                    return runSetUserRestriction();
+                case "get-max-users":
+                    return runGetMaxUsers();
+                case "set-home-activity":
+                    return runSetHomeActivity();
+                case "set-installer":
+                    return runSetInstaller();
                 case "get-instantapp-resolver":
                     return runGetInstantAppResolver();
                 case "has-feature":
                     return runHasFeature();
-                default:
+                default: {
+                    String nextArg = getNextArg();
+                    if (nextArg == null) {
+                        if (cmd.equalsIgnoreCase("-l")) {
+                            return runListPackages(false);
+                        } else if (cmd.equalsIgnoreCase("-lf")) {
+                            return runListPackages(true);
+                        }
+                    } else if (getNextArg() == null) {
+                        if (cmd.equalsIgnoreCase("-p")) {
+                            return displayPackageFilePath(nextArg, UserHandle.USER_SYSTEM);
+                        }
+                    }
                     return handleDefaultCommands(cmd);
+                }
             }
         } catch (RemoteException e) {
             pw.println("Remote exception: " + e);
@@ -195,346 +282,40 @@
             }
         }
     }
-
-    private int runInstall() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final InstallParams params = makeInstallParams();
-        final String inPath = getNextArg();
-
-        setParamsSize(params, inPath);
-        final int sessionId = doCreateSession(params.sessionParams,
-                params.installerPackageName, params.userId);
-        boolean abandonSession = true;
-        try {
-            if (inPath == null && params.sessionParams.sizeBytes == -1) {
-                pw.println("Error: must either specify a package size or an APK file");
-                return 1;
-            }
-            if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
-                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            if (doCommitSession(sessionId, false /*logSuccess*/)
-                    != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            abandonSession = false;
-            pw.println("Success");
-            return 0;
-        } finally {
-            if (abandonSession) {
-                try {
-                    doAbandonSession(sessionId, false /*logSuccess*/);
-                } catch (Exception ignore) {
+    /**
+     * Displays the package file for a package.
+     * @param pckg
+     */
+    private int displayPackageFilePath(String pckg, int userId) throws RemoteException {
+        PackageInfo info = mInterface.getPackageInfo(pckg, 0, userId);
+        if (info != null && info.applicationInfo != null) {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.print("package:");
+            pw.println(info.applicationInfo.sourceDir);
+            if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
+                for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
+                    pw.print("package:");
+                    pw.println(splitSourceDir);
                 }
             }
+            return 0;
         }
+        return 1;
     }
 
-    private int runSuspend(boolean suspendedState) {
-        final PrintWriter pw = getOutPrintWriter();
+    private int runPath() throws RemoteException {
         int userId = UserHandle.USER_SYSTEM;
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
         }
 
-        String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
+        String pkg = getNextArgRequired();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
             return 1;
         }
-
-        try {
-            mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
-                    userId);
-            pw.println("Package " + packageName + " new suspended state: "
-                    + mInterface.isPackageSuspendedForUser(packageName, userId));
-            return 0;
-        } catch (RemoteException | IllegalArgumentException e) {
-            pw.println(e.toString());
-            return 1;
-        }
-    }
-
-    private int runInstallAbandon() throws RemoteException {
-        final int sessionId = Integer.parseInt(getNextArg());
-        return doAbandonSession(sessionId, true /*logSuccess*/);
-    }
-
-    private int runInstallCommit() throws RemoteException {
-        final int sessionId = Integer.parseInt(getNextArg());
-        return doCommitSession(sessionId, true /*logSuccess*/);
-    }
-
-    private int runInstallCreate() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final InstallParams installParams = makeInstallParams();
-        final int sessionId = doCreateSession(installParams.sessionParams,
-                installParams.installerPackageName, installParams.userId);
-
-        // NOTE: adb depends on parsing this string
-        pw.println("Success: created install session [" + sessionId + "]");
-        return 0;
-    }
-
-    private int runInstallWrite() throws RemoteException {
-        long sizeBytes = -1;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            if (opt.equals("-S")) {
-                sizeBytes = Long.parseLong(getNextArg());
-            } else {
-                throw new IllegalArgumentException("Unknown option: " + opt);
-            }
-        }
-
-        final int sessionId = Integer.parseInt(getNextArg());
-        final String splitName = getNextArg();
-        final String path = getNextArg();
-        return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
-    }
-
-    private int runInstallRemove() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-
-        final int sessionId = Integer.parseInt(getNextArg());
-
-        final String splitName = getNextArg();
-        if (splitName == null) {
-            pw.println("Error: split name not specified");
-            return 1;
-        }
-        return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
-    }
-
-    private int runInstallExisting() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        int userId = UserHandle.USER_SYSTEM;
-        int installFlags = 0;
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-                case "--ephemeral":
-                case "--instant":
-                    installFlags |= PackageManager.INSTALL_INSTANT_APP;
-                    installFlags &= ~PackageManager.INSTALL_FULL_APP;
-                    break;
-                case "--full":
-                    installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
-                    installFlags |= PackageManager.INSTALL_FULL_APP;
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        final String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
-            return 1;
-        }
-
-        try {
-            final int res = mInterface.installExistingPackageAsUser(packageName, userId,
-                    installFlags, PackageManager.INSTALL_REASON_UNKNOWN);
-            if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
-                throw new NameNotFoundException("Package " + packageName + " doesn't exist");
-            }
-            pw.println("Package " + packageName + " installed for user: " + userId);
-            return 0;
-        } catch (RemoteException | NameNotFoundException e) {
-            pw.println(e.toString());
-            return 1;
-        }
-    }
-
-    private int runCompile() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
-        boolean forceCompilation = false;
-        boolean allPackages = false;
-        boolean clearProfileData = false;
-        String compilerFilter = null;
-        String compilationReason = null;
-        String checkProfilesRaw = null;
-        boolean secondaryDex = false;
-        String split = null;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-a":
-                    allPackages = true;
-                    break;
-                case "-c":
-                    clearProfileData = true;
-                    break;
-                case "-f":
-                    forceCompilation = true;
-                    break;
-                case "-m":
-                    compilerFilter = getNextArgRequired();
-                    break;
-                case "-r":
-                    compilationReason = getNextArgRequired();
-                    break;
-                case "--check-prof":
-                    checkProfilesRaw = getNextArgRequired();
-                    break;
-                case "--reset":
-                    forceCompilation = true;
-                    clearProfileData = true;
-                    compilationReason = "install";
-                    break;
-                case "--secondary-dex":
-                    secondaryDex = true;
-                    break;
-                case "--split":
-                    split = getNextArgRequired();
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        if (checkProfilesRaw != null) {
-            if ("true".equals(checkProfilesRaw)) {
-                checkProfiles = true;
-            } else if ("false".equals(checkProfilesRaw)) {
-                checkProfiles = false;
-            } else {
-                pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
-                return 1;
-            }
-        }
-
-        if (compilerFilter != null && compilationReason != null) {
-            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " +
-                    "at the same time");
-            return 1;
-        }
-        if (compilerFilter == null && compilationReason == null) {
-            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " +
-                    "reason (\"-r\") at the same time");
-            return 1;
-        }
-
-        if (allPackages && split != null) {
-            pw.println("-a cannot be specified together with --split");
-            return 1;
-        }
-
-        if (secondaryDex && split != null) {
-            pw.println("--secondary-dex cannot be specified together with --split");
-            return 1;
-        }
-
-        String targetCompilerFilter;
-        if (compilerFilter != null) {
-            if (!DexFile.isValidCompilerFilter(compilerFilter)) {
-                pw.println("Error: \"" + compilerFilter +
-                        "\" is not a valid compilation filter.");
-                return 1;
-            }
-            targetCompilerFilter = compilerFilter;
-        } else {
-            int reason = -1;
-            for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
-                if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
-                        compilationReason)) {
-                    reason = i;
-                    break;
-                }
-            }
-            if (reason == -1) {
-                pw.println("Error: Unknown compilation reason: " + compilationReason);
-                return 1;
-            }
-            targetCompilerFilter =
-                    PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
-        }
-
-
-        List<String> packageNames = null;
-        if (allPackages) {
-            packageNames = mInterface.getAllPackages();
-        } else {
-            String packageName = getNextArg();
-            if (packageName == null) {
-                pw.println("Error: package name not specified");
-                return 1;
-            }
-            packageNames = Collections.singletonList(packageName);
-        }
-
-        List<String> failedPackages = new ArrayList<>();
-        for (String packageName : packageNames) {
-            if (clearProfileData) {
-                mInterface.clearApplicationProfileData(packageName);
-            }
-
-            boolean result = secondaryDex
-                    ? mInterface.performDexOptSecondary(packageName,
-                            targetCompilerFilter, forceCompilation)
-                    : mInterface.performDexOptMode(packageName,
-                            checkProfiles, targetCompilerFilter, forceCompilation,
-                            true /* bootComplete */, split);
-            if (!result) {
-                failedPackages.add(packageName);
-            }
-        }
-
-        if (failedPackages.isEmpty()) {
-            pw.println("Success");
-            return 0;
-        } else if (failedPackages.size() == 1) {
-            pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
-            return 1;
-        } else {
-            pw.print("Failure: the following packages could not be compiled: ");
-            boolean is_first = true;
-            for (String packageName : failedPackages) {
-                if (is_first) {
-                    is_first = false;
-                } else {
-                    pw.print(", ");
-                }
-                pw.print(packageName);
-            }
-            pw.println();
-            return 1;
-        }
-    }
-
-    private int runreconcileSecondaryDexFiles() throws RemoteException {
-        String packageName = getNextArg();
-        mInterface.reconcileSecondaryDexFiles(packageName);
-        return 0;
-    }
-
-    private int runDexoptJob() throws RemoteException {
-        boolean result = mInterface.runBackgroundDexoptJob();
-        return result ? 0 : -1;
-    }
-
-    private int runDumpProfiles() throws RemoteException {
-        String packageName = getNextArg();
-        mInterface.dumpProfiles(packageName);
-        return 0;
+        return displayPackageFilePath(pkg, userId);
     }
 
     private int runList() throws RemoteException {
@@ -558,6 +339,11 @@
                 return runListPermissionGroups();
             case "permissions":
                 return runListPermissions();
+            case "users":
+                ServiceManager.getService("user").shellCommand(
+                        getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
+                        new String[] { "list" }, getShellCallback(), adoptResultReceiver());
+                return 0;
         }
         pw.println("Error: unknown list type '" + type + "'");
         return -1;
@@ -590,7 +376,7 @@
                 pw.println();
             } else {
                 pw.println("reqGlEsVersion=0x"
-                    + Integer.toHexString(fi.reqGlEsVersion));
+                        + Integer.toHexString(fi.reqGlEsVersion));
             }
         }
         return 0;
@@ -872,111 +658,6 @@
         return 0;
     }
 
-    private int runUninstall() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        int flags = 0;
-        int userId = UserHandle.USER_ALL;
-        int versionCode = PackageManager.VERSION_CODE_HIGHEST;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-k":
-                    flags |= PackageManager.DELETE_KEEP_DATA;
-                    break;
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-                case "--versionCode":
-                    versionCode = Integer.parseInt(getNextArgRequired());
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        final String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
-            return 1;
-        }
-
-        // if a split is specified, just remove it and not the whole package
-        final String splitName = getNextArg();
-        if (splitName != null) {
-            return runRemoveSplit(packageName, splitName);
-        }
-
-        userId = translateUserId(userId, "runUninstall");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            flags |= PackageManager.DELETE_ALL_USERS;
-        } else {
-            final PackageInfo info = mInterface.getPackageInfo(packageName,
-                    PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
-            if (info == null) {
-                pw.println("Failure [not installed for " + userId + "]");
-                return 1;
-            }
-            final boolean isSystem =
-                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-            // If we are being asked to delete a system app for just one
-            // user set flag so it disables rather than reverting to system
-            // version of the app.
-            if (isSystem) {
-                flags |= PackageManager.DELETE_SYSTEM_APP;
-            }
-        }
-
-        final LocalIntentReceiver receiver = new LocalIntentReceiver();
-        mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
-                versionCode), null /*callerPackageName*/, flags,
-                receiver.getIntentSender(), userId);
-
-        final Intent result = receiver.getResult();
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == PackageInstaller.STATUS_SUCCESS) {
-            pw.println("Success");
-            return 0;
-        } else {
-            pw.println("Failure ["
-                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
-            return 1;
-        }
-    }
-
-    private int runRemoveSplit(String packageName, String splitName) throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
-        sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-        sessionParams.appPackageName = packageName;
-        final int sessionId =
-                doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL);
-        boolean abandonSession = true;
-        try {
-            if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/)
-                    != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            if (doCommitSession(sessionId, false /*logSuccess*/)
-                    != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            abandonSession = false;
-            pw.println("Success");
-            return 0;
-        } finally {
-            if (abandonSession) {
-                try {
-                    doAbandonSession(sessionId, false /*logSuccess*/);
-                } catch (Exception ignore) {
-                }
-            }
-        }
-    }
-
     private Intent parseIntentAndUser() throws URISyntaxException {
         mTargetUser = UserHandle.USER_CURRENT;
         mBrief = false;
@@ -1154,6 +835,1029 @@
         return 0;
     }
 
+    private int runInstall() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final InstallParams params = makeInstallParams();
+        final String inPath = getNextArg();
+
+        setParamsSize(params, inPath);
+        final int sessionId = doCreateSession(params.sessionParams,
+                params.installerPackageName, params.userId);
+        boolean abandonSession = true;
+        try {
+            if (inPath == null && params.sessionParams.sizeBytes == -1) {
+                pw.println("Error: must either specify a package size or an APK file");
+                return 1;
+            }
+            if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
+                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            if (doCommitSession(sessionId, false /*logSuccess*/)
+                    != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            abandonSession = false;
+            pw.println("Success");
+            return 0;
+        } finally {
+            if (abandonSession) {
+                try {
+                    doAbandonSession(sessionId, false /*logSuccess*/);
+                } catch (Exception ignore) {
+                }
+            }
+        }
+    }
+
+    private int runInstallAbandon() throws RemoteException {
+        final int sessionId = Integer.parseInt(getNextArg());
+        return doAbandonSession(sessionId, true /*logSuccess*/);
+    }
+
+    private int runInstallCommit() throws RemoteException {
+        final int sessionId = Integer.parseInt(getNextArg());
+        return doCommitSession(sessionId, true /*logSuccess*/);
+    }
+
+    private int runInstallCreate() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final InstallParams installParams = makeInstallParams();
+        final int sessionId = doCreateSession(installParams.sessionParams,
+                installParams.installerPackageName, installParams.userId);
+
+        // NOTE: adb depends on parsing this string
+        pw.println("Success: created install session [" + sessionId + "]");
+        return 0;
+    }
+
+    private int runInstallWrite() throws RemoteException {
+        long sizeBytes = -1;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("-S")) {
+                sizeBytes = Long.parseLong(getNextArg());
+            } else {
+                throw new IllegalArgumentException("Unknown option: " + opt);
+            }
+        }
+
+        final int sessionId = Integer.parseInt(getNextArg());
+        final String splitName = getNextArg();
+        final String path = getNextArg();
+        return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
+    }
+
+    private int runInstallRemove() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+
+        final int sessionId = Integer.parseInt(getNextArg());
+
+        final String splitName = getNextArg();
+        if (splitName == null) {
+            pw.println("Error: split name not specified");
+            return 1;
+        }
+        return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
+    }
+
+    private int runInstallExisting() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_SYSTEM;
+        int installFlags = 0;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--ephemeral":
+                case "--instant":
+                    installFlags |= PackageManager.INSTALL_INSTANT_APP;
+                    installFlags &= ~PackageManager.INSTALL_FULL_APP;
+                    break;
+                case "--full":
+                    installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
+                    installFlags |= PackageManager.INSTALL_FULL_APP;
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        try {
+            final int res = mInterface.installExistingPackageAsUser(packageName, userId,
+                    installFlags, PackageManager.INSTALL_REASON_UNKNOWN);
+            if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
+                throw new NameNotFoundException("Package " + packageName + " doesn't exist");
+            }
+            pw.println("Package " + packageName + " installed for user: " + userId);
+            return 0;
+        } catch (RemoteException | NameNotFoundException e) {
+            pw.println(e.toString());
+            return 1;
+        }
+    }
+
+    private int runSetInstallLocation() throws RemoteException {
+        int loc;
+
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no install location specified.");
+            return 1;
+        }
+        try {
+            loc = Integer.parseInt(arg);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: install location has to be a number.");
+            return 1;
+        }
+        if (!mInterface.setInstallLocation(loc)) {
+            getErrPrintWriter().println("Error: install location has to be a number.");
+            return 1;
+        }
+        return 0;
+    }
+
+    private int runGetInstallLocation() throws RemoteException {
+        int loc = mInterface.getInstallLocation();
+        String locStr = "invalid";
+        if (loc == PackageHelper.APP_INSTALL_AUTO) {
+            locStr = "auto";
+        } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
+            locStr = "internal";
+        } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+            locStr = "external";
+        }
+        getOutPrintWriter().println(loc + "[" + locStr + "]");
+        return 0;
+    }
+
+    public int runMovePackage() throws RemoteException {
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            getErrPrintWriter().println("Error: package name not specified");
+            return 1;
+        }
+        String volumeUuid = getNextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+
+        final int moveId = mInterface.movePackage(packageName, volumeUuid);
+
+        int status = mInterface.getMoveStatus(moveId);
+        while (!PackageManager.isMoveStatusFinished(status)) {
+            SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+            status = mInterface.getMoveStatus(moveId);
+        }
+
+        if (status == PackageManager.MOVE_SUCCEEDED) {
+            getOutPrintWriter().println("Success");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Failure [" + status + "]");
+            return 1;
+        }
+    }
+
+    public int runMovePrimaryStorage() throws RemoteException {
+        String volumeUuid = getNextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+
+        final int moveId = mInterface.movePrimaryStorage(volumeUuid);
+
+        int status = mInterface.getMoveStatus(moveId);
+        while (!PackageManager.isMoveStatusFinished(status)) {
+            SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+            status = mInterface.getMoveStatus(moveId);
+        }
+
+        if (status == PackageManager.MOVE_SUCCEEDED) {
+            getOutPrintWriter().println("Success");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Failure [" + status + "]");
+            return 1;
+        }
+    }
+
+    private int runCompile() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+        boolean forceCompilation = false;
+        boolean allPackages = false;
+        boolean clearProfileData = false;
+        String compilerFilter = null;
+        String compilationReason = null;
+        String checkProfilesRaw = null;
+        boolean secondaryDex = false;
+        String split = null;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-a":
+                    allPackages = true;
+                    break;
+                case "-c":
+                    clearProfileData = true;
+                    break;
+                case "-f":
+                    forceCompilation = true;
+                    break;
+                case "-m":
+                    compilerFilter = getNextArgRequired();
+                    break;
+                case "-r":
+                    compilationReason = getNextArgRequired();
+                    break;
+                case "--check-prof":
+                    checkProfilesRaw = getNextArgRequired();
+                    break;
+                case "--reset":
+                    forceCompilation = true;
+                    clearProfileData = true;
+                    compilationReason = "install";
+                    break;
+                case "--secondary-dex":
+                    secondaryDex = true;
+                    break;
+                case "--split":
+                    split = getNextArgRequired();
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        if (checkProfilesRaw != null) {
+            if ("true".equals(checkProfilesRaw)) {
+                checkProfiles = true;
+            } else if ("false".equals(checkProfilesRaw)) {
+                checkProfiles = false;
+            } else {
+                pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
+                return 1;
+            }
+        }
+
+        if (compilerFilter != null && compilationReason != null) {
+            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " +
+                    "at the same time");
+            return 1;
+        }
+        if (compilerFilter == null && compilationReason == null) {
+            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " +
+                    "reason (\"-r\") at the same time");
+            return 1;
+        }
+
+        if (allPackages && split != null) {
+            pw.println("-a cannot be specified together with --split");
+            return 1;
+        }
+
+        if (secondaryDex && split != null) {
+            pw.println("--secondary-dex cannot be specified together with --split");
+            return 1;
+        }
+
+        String targetCompilerFilter;
+        if (compilerFilter != null) {
+            if (!DexFile.isValidCompilerFilter(compilerFilter)) {
+                pw.println("Error: \"" + compilerFilter +
+                        "\" is not a valid compilation filter.");
+                return 1;
+            }
+            targetCompilerFilter = compilerFilter;
+        } else {
+            int reason = -1;
+            for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
+                if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
+                        compilationReason)) {
+                    reason = i;
+                    break;
+                }
+            }
+            if (reason == -1) {
+                pw.println("Error: Unknown compilation reason: " + compilationReason);
+                return 1;
+            }
+            targetCompilerFilter =
+                    PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
+        }
+
+
+        List<String> packageNames = null;
+        if (allPackages) {
+            packageNames = mInterface.getAllPackages();
+        } else {
+            String packageName = getNextArg();
+            if (packageName == null) {
+                pw.println("Error: package name not specified");
+                return 1;
+            }
+            packageNames = Collections.singletonList(packageName);
+        }
+
+        List<String> failedPackages = new ArrayList<>();
+        for (String packageName : packageNames) {
+            if (clearProfileData) {
+                mInterface.clearApplicationProfileData(packageName);
+            }
+
+            boolean result = secondaryDex
+                    ? mInterface.performDexOptSecondary(packageName,
+                            targetCompilerFilter, forceCompilation)
+                    : mInterface.performDexOptMode(packageName,
+                            checkProfiles, targetCompilerFilter, forceCompilation,
+                            true /* bootComplete */, split);
+            if (!result) {
+                failedPackages.add(packageName);
+            }
+        }
+
+        if (failedPackages.isEmpty()) {
+            pw.println("Success");
+            return 0;
+        } else if (failedPackages.size() == 1) {
+            pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
+            return 1;
+        } else {
+            pw.print("Failure: the following packages could not be compiled: ");
+            boolean is_first = true;
+            for (String packageName : failedPackages) {
+                if (is_first) {
+                    is_first = false;
+                } else {
+                    pw.print(", ");
+                }
+                pw.print(packageName);
+            }
+            pw.println();
+            return 1;
+        }
+    }
+
+    private int runreconcileSecondaryDexFiles() throws RemoteException {
+        String packageName = getNextArg();
+        mInterface.reconcileSecondaryDexFiles(packageName);
+        return 0;
+    }
+
+    public int runForceDexOpt() throws RemoteException {
+        mInterface.forceDexOpt(getNextArgRequired());
+        return 0;
+    }
+
+    private int runDexoptJob() throws RemoteException {
+        boolean result = mInterface.runBackgroundDexoptJob();
+        return result ? 0 : -1;
+    }
+
+    private int runDumpProfiles() throws RemoteException {
+        String packageName = getNextArg();
+        mInterface.dumpProfiles(packageName);
+        return 0;
+    }
+
+    private int runUninstall() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int flags = 0;
+        int userId = UserHandle.USER_ALL;
+        int versionCode = PackageManager.VERSION_CODE_HIGHEST;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-k":
+                    flags |= PackageManager.DELETE_KEEP_DATA;
+                    break;
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--versionCode":
+                    versionCode = Integer.parseInt(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        // if a split is specified, just remove it and not the whole package
+        final String splitName = getNextArg();
+        if (splitName != null) {
+            return runRemoveSplit(packageName, splitName);
+        }
+
+        userId = translateUserId(userId, "runUninstall");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+            flags |= PackageManager.DELETE_ALL_USERS;
+        } else {
+            final PackageInfo info = mInterface.getPackageInfo(packageName,
+                    PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
+            if (info == null) {
+                pw.println("Failure [not installed for " + userId + "]");
+                return 1;
+            }
+            final boolean isSystem =
+                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+            // If we are being asked to delete a system app for just one
+            // user set flag so it disables rather than reverting to system
+            // version of the app.
+            if (isSystem) {
+                flags |= PackageManager.DELETE_SYSTEM_APP;
+            }
+        }
+
+        final LocalIntentReceiver receiver = new LocalIntentReceiver();
+        mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
+                versionCode), null /*callerPackageName*/, flags,
+                receiver.getIntentSender(), userId);
+
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            pw.println("Success");
+            return 0;
+        } else {
+            pw.println("Failure ["
+                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+            return 1;
+        }
+    }
+
+    private int runRemoveSplit(String packageName, String splitName) throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
+        sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+        sessionParams.appPackageName = packageName;
+        final int sessionId =
+                doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL);
+        boolean abandonSession = true;
+        try {
+            if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/)
+                    != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            if (doCommitSession(sessionId, false /*logSuccess*/)
+                    != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            abandonSession = false;
+            pw.println("Success");
+            return 0;
+        } finally {
+            if (abandonSession) {
+                try {
+                    doAbandonSession(sessionId, false /*logSuccess*/);
+                } catch (Exception ignore) {
+                }
+            }
+        }
+    }
+
+    static class ClearDataObserver extends IPackageDataObserver.Stub {
+        boolean finished;
+        boolean result;
+
+        @Override
+        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
+            synchronized (this) {
+                finished = true;
+                result = succeeded;
+                notifyAll();
+            }
+        }
+    }
+
+    private int runClear() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+
+        ClearDataObserver obs = new ClearDataObserver();
+        ActivityManager.getService().clearApplicationUserData(pkg, obs, userId);
+        synchronized (obs) {
+            while (!obs.finished) {
+                try {
+                    obs.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        if (obs.result) {
+            getOutPrintWriter().println("Success");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Failed");
+            return 1;
+        }
+    }
+
+    private static String enabledSettingToString(int state) {
+        switch (state) {
+            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+                return "default";
+            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+                return "enabled";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+                return "disabled";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+                return "disabled-user";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+                return "disabled-until-used";
+        }
+        return "unknown";
+    }
+
+    private int runSetEnabledSetting(int state) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package or component specified");
+            return 1;
+        }
+        ComponentName cn = ComponentName.unflattenFromString(pkg);
+        if (cn == null) {
+            mInterface.setApplicationEnabledSetting(pkg, state, 0, userId,
+                    "shell:" + android.os.Process.myUid());
+            getOutPrintWriter().println("Package " + pkg + " new state: "
+                    + enabledSettingToString(
+                    mInterface.getApplicationEnabledSetting(pkg, userId)));
+            return 0;
+        } else {
+            mInterface.setComponentEnabledSetting(cn, state, 0, userId);
+            getOutPrintWriter().println("Component " + cn.toShortString() + " new state: "
+                    + enabledSettingToString(
+                    mInterface.getComponentEnabledSetting(cn, userId)));
+            return 0;
+        }
+    }
+
+    private int runSetHiddenSetting(boolean state) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package or component specified");
+            return 1;
+        }
+        mInterface.setApplicationHiddenSettingAsUser(pkg, state, userId);
+        getOutPrintWriter().println("Package " + pkg + " new hidden state: "
+                + mInterface.getApplicationHiddenSettingAsUser(pkg, userId));
+        return 0;
+    }
+
+    private int runSuspend(boolean suspendedState) {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_SYSTEM;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        try {
+            mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
+                    userId);
+            pw.println("Package " + packageName + " new suspended state: "
+                    + mInterface.isPackageSuspendedForUser(packageName, userId));
+            return 0;
+        } catch (RemoteException | IllegalArgumentException e) {
+            pw.println(e.toString());
+            return 1;
+        }
+    }
+
+    private int runGrantRevokePermission(boolean grant) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt = null;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            }
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+        String perm = getNextArg();
+        if (perm == null) {
+            getErrPrintWriter().println("Error: no permission specified");
+            return 1;
+        }
+
+        if (grant) {
+            mInterface.grantRuntimePermission(pkg, perm, userId);
+        } else {
+            mInterface.revokeRuntimePermission(pkg, perm, userId);
+        }
+        return 0;
+    }
+
+    private int runResetPermissions() throws RemoteException {
+        mInterface.resetRuntimePermissions();
+        return 0;
+    }
+
+    private int runSetPermissionEnforced() throws RemoteException {
+        final String permission = getNextArg();
+        if (permission == null) {
+            getErrPrintWriter().println("Error: no permission specified");
+            return 1;
+        }
+        final String enforcedRaw = getNextArg();
+        if (enforcedRaw == null) {
+            getErrPrintWriter().println("Error: no enforcement specified");
+            return 1;
+        }
+        mInterface.setPermissionEnforced(permission, Boolean.parseBoolean(enforcedRaw));
+        return 0;
+    }
+
+    private int runGetPrivappPermissions() {
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+        ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
+        getOutPrintWriter().println(privAppPermissions == null
+                ? "{}" : privAppPermissions.toString());
+        return 0;
+    }
+
+    private int runGetPrivappDenyPermissions() {
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+        ArraySet<String> privAppDenyPermissions =
+                SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
+        getOutPrintWriter().println(privAppDenyPermissions == null
+                ? "{}" : privAppDenyPermissions.toString());
+        return 0;
+    }
+
+    private int runGetOemPermissions() {
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+        final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
+                .getOemPermissions(pkg);
+        if (oemPermissions == null || oemPermissions.isEmpty()) {
+            getOutPrintWriter().println("{}");
+        } else {
+            oemPermissions.forEach((permission, granted) ->
+                    getOutPrintWriter().println(permission + " granted:" + granted)
+            );
+        }
+        return 0;
+    }
+
+    private String linkStateToString(int state) {
+        switch (state) {
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
+        }
+        return "Unknown link state: " + state;
+    }
+
+    // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
+    private int runSetAppLink() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        // Package name to act on; required
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+
+        // State to apply; {always|ask|never|undefined}, required
+        final String modeString = getNextArg();
+        if (modeString == null) {
+            getErrPrintWriter().println("Error: no app link state specified.");
+            return 1;
+        }
+
+        final int newMode;
+        switch (modeString.toLowerCase()) {
+            case "undefined":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+                break;
+
+            case "always":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+                break;
+
+            case "ask":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+                break;
+
+            case "always-ask":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+                break;
+
+            case "never":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+                break;
+
+            default:
+                getErrPrintWriter().println("Error: unknown app link state '" + modeString + "'");
+                return 1;
+        }
+
+        final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
+        if (info == null) {
+            getErrPrintWriter().println("Error: package " + pkg + " not found.");
+            return 1;
+        }
+
+        if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
+            getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
+            return 1;
+        }
+
+        if (!mInterface.updateIntentVerificationStatus(pkg, newMode, userId)) {
+            getErrPrintWriter().println("Error: unable to update app link status for " + pkg);
+            return 1;
+        }
+
+        return 0;
+    }
+
+    // pm get-app-link [--user USER_ID] PACKAGE
+    private int runGetAppLink() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        // Package name to act on; required
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+
+        final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
+        if (info == null) {
+            getErrPrintWriter().println("Error: package " + pkg + " not found.");
+            return 1;
+        }
+
+        if ((info.applicationInfo.privateFlags
+                & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
+            getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
+            return 1;
+        }
+
+        getOutPrintWriter().println(linkStateToString(
+                mInterface.getIntentVerificationStatus(pkg, userId)));
+
+        return 0;
+    }
+
+    private int runTrimCaches() throws RemoteException {
+        String size = getNextArg();
+        if (size == null) {
+            getErrPrintWriter().println("Error: no size specified");
+            return 1;
+        }
+        long multiplier = 1;
+        int len = size.length();
+        char c = size.charAt(len - 1);
+        if (c < '0' || c > '9') {
+            if (c == 'K' || c == 'k') {
+                multiplier = 1024L;
+            } else if (c == 'M' || c == 'm') {
+                multiplier = 1024L*1024L;
+            } else if (c == 'G' || c == 'g') {
+                multiplier = 1024L*1024L*1024L;
+            } else {
+                getErrPrintWriter().println("Invalid suffix: " + c);
+                return 1;
+            }
+            size = size.substring(0, len-1);
+        }
+        long sizeVal;
+        try {
+            sizeVal = Long.parseLong(size) * multiplier;
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: expected number at: " + size);
+            return 1;
+        }
+        String volumeUuid = getNextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+        ClearDataObserver obs = new ClearDataObserver();
+        mInterface.freeStorageAndNotify(volumeUuid, sizeVal,
+                StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs);
+        synchronized (obs) {
+            while (!obs.finished) {
+                try {
+                    obs.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        return 0;
+    }
+
+    private static boolean isNumber(String s) {
+        try {
+            Integer.parseInt(s);
+        } catch (NumberFormatException nfe) {
+            return false;
+        }
+        return true;
+    }
+
+    public int runCreateUser() throws RemoteException {
+        String name;
+        int userId = -1;
+        int flags = 0;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if ("--profileOf".equals(opt)) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else if ("--managed".equals(opt)) {
+                flags |= UserInfo.FLAG_MANAGED_PROFILE;
+            } else if ("--restricted".equals(opt)) {
+                flags |= UserInfo.FLAG_RESTRICTED;
+            } else if ("--ephemeral".equals(opt)) {
+                flags |= UserInfo.FLAG_EPHEMERAL;
+            } else if ("--guest".equals(opt)) {
+                flags |= UserInfo.FLAG_GUEST;
+            } else if ("--demo".equals(opt)) {
+                flags |= UserInfo.FLAG_DEMO;
+            } else {
+                getErrPrintWriter().println("Error: unknown option " + opt);
+                return 1;
+            }
+        }
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no user name specified.");
+            return 1;
+        }
+        name = arg;
+        UserInfo info;
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        IAccountManager accm = IAccountManager.Stub.asInterface(
+                ServiceManager.getService(Context.ACCOUNT_SERVICE));
+        if ((flags & UserInfo.FLAG_RESTRICTED) != 0) {
+            // In non-split user mode, userId can only be SYSTEM
+            int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
+            info = um.createRestrictedProfile(name, parentUserId);
+            accm.addSharedAccountsFromParentUser(parentUserId, userId,
+                    (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
+        } else if (userId < 0) {
+            info = um.createUser(name, flags);
+        } else {
+            info = um.createProfileForUser(name, flags, userId, null);
+        }
+
+        if (info != null) {
+            getOutPrintWriter().println("Success: created user id " + info.id);
+            return 0;
+        } else {
+            getErrPrintWriter().println("Error: couldn't create User.");
+            return 1;
+        }
+    }
+
+    public int runRemoveUser() throws RemoteException {
+        int userId;
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no user id specified.");
+            return 1;
+        }
+        userId = UserHandle.parseUserArg(arg);
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        if (um.removeUser(userId)) {
+            getOutPrintWriter().println("Success: removed user");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Error: couldn't remove user id " + userId);
+            return 1;
+        }
+    }
+
+    public int runSetUserRestriction() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String opt = getNextOption();
+        if (opt != null && "--user".equals(opt)) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String restriction = getNextArg();
+        String arg = getNextArg();
+        boolean value;
+        if ("1".equals(arg)) {
+            value = true;
+        } else if ("0".equals(arg)) {
+            value = false;
+        } else {
+            getErrPrintWriter().println("Error: valid value not specified");
+            return 1;
+        }
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        um.setUserRestriction(restriction, value, userId);
+        return 0;
+    }
+
+    public int runGetMaxUsers() {
+        getOutPrintWriter().println("Maximum supported users: "
+                + UserManager.getMaxSupportedUsers());
+        return 0;
+    }
+
     private static class InstallParams {
         SessionParams sessionParams;
         String installerPackageName;
@@ -1287,46 +1991,17 @@
         }
     }
 
-    private int runGetPrivappPermissions() {
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return 1;
-        }
-        ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
-        getOutPrintWriter().println(privAppPermissions == null
-                ? "{}" : privAppPermissions.toString());
-        return 0;
-    }
+    private int runSetInstaller() throws RemoteException {
+        final String targetPackage = getNextArg();
+        final String installerPackageName = getNextArg();
 
-    private int runGetPrivappDenyPermissions() {
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
+        if (targetPackage == null || installerPackageName == null) {
+            getErrPrintWriter().println("Must provide both target and installer package names");
             return 1;
         }
-        ArraySet<String> privAppDenyPermissions =
-                SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
-        getOutPrintWriter().println(privAppDenyPermissions == null
-                ? "{}" : privAppDenyPermissions.toString());
-        return 0;
-    }
 
-    private int runGetOemPermissions() {
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return 1;
-        }
-        final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
-                .getOemPermissions(pkg);
-        if (oemPermissions == null || oemPermissions.isEmpty()) {
-            getOutPrintWriter().println("{}");
-        } else {
-            oemPermissions.forEach((permission, granted) ->
-                getOutPrintWriter().println(permission + " granted:" + granted)
-            );
-        }
+        mInterface.setInstallerPackageName(targetPackage, installerPackageName);
+        getOutPrintWriter().println("Success");
         return 0;
     }
 
@@ -1367,6 +2042,16 @@
         }
     }
 
+    private int runDump() {
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+        ActivityManager.dumpPackageStateStatic(getOutFileDescriptor(), pkg);
+        return 0;
+    }
+
     private static String checkAbiArgument(String abi) {
         if (TextUtils.isEmpty(abi)) {
             throw new IllegalArgumentException("Missing ABI argument");
@@ -1663,10 +2348,203 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println("");
+        pw.println("  path [--user USER_ID] PACKAGE");
+        pw.println("    Print the path to the .apk of the given PACKAGE.");
+        pw.println("");
+        pw.println("  dump PACKAGE");
+        pw.println("    Print various system state associated with the given PACKAGE.");
+        pw.println("");
+        pw.println("  list features");
+        pw.println("    Prints all features of the system.");
+        pw.println("");
+        pw.println("  has-feature FEATURE_NAME [version]");
+        pw.println("    Prints true and returns exit status 0 when system has a FEATURE_NAME,");
+        pw.println("    otherwise prints false and returns exit status 1");
+        pw.println("");
+        pw.println("  list instrumentation [-f] [TARGET-PACKAGE]");
+        pw.println("    Prints all test packages; optionally only those targeting TARGET-PACKAGE");
+        pw.println("    Options:");
+        pw.println("      -f: dump the name of the .apk file containing the test package");
+        pw.println("");
+        pw.println("  list libraries");
+        pw.println("    Prints all system libraries.");
+        pw.println("");
+        pw.println("  list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] ");
+        pw.println("      [--uid UID] [--user USER_ID] [FILTER]");
+        pw.println("    Prints all packages; optionally only those whose name contains");
+        pw.println("    the text in FILTER.  Options are:");
+        pw.println("      -f: see their associated file");
+        pw.println("      -d: filter to only show disabled packages");
+        pw.println("      -e: filter to only show enabled packages");
+        pw.println("      -s: filter to only show system packages");
+        pw.println("      -3: filter to only show third party packages");
+        pw.println("      -i: see the installer for the packages");
+        pw.println("      -l: ignored (used for compatibility with older releases)");
+        pw.println("      -U: also show the package UID");
+        pw.println("      -u: also include uninstalled packages");
+        pw.println("      --uid UID: filter to only show packages with the given UID");
+        pw.println("      --user USER_ID: only list packages belonging to the given user");
+        pw.println("");
+        pw.println("  list permission-groups");
+        pw.println("    Prints all known permission groups.");
+        pw.println("");
+        pw.println("  list permissions [-g] [-f] [-d] [-u] [GROUP]");
+        pw.println("    Prints all known permissions; optionally only those in GROUP.  Options are:");
+        pw.println("      -g: organize by group");
+        pw.println("      -f: print all information");
+        pw.println("      -s: short summary");
+        pw.println("      -d: only list dangerous permissions");
+        pw.println("      -u: list only the permissions users will see");
+        pw.println("");
+        pw.println("  resolve-activity [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints the activity that resolves to the given INTENT.");
+        pw.println("");
+        pw.println("  query-activities [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints all activities that can handle the given INTENT.");
+        pw.println("");
+        pw.println("  query-services [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints all services that can handle the given INTENT.");
+        pw.println("");
+        pw.println("  query-receivers [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints all broadcast receivers that can handle the given INTENT.");
+        pw.println("");
+        pw.println("  install [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]");
+        pw.println("       [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        pw.println("       [--originating-uri URI] [---referrer URI]");
+        pw.println("       [--abi ABI_NAME] [--force-sdk]");
+        pw.println("       [--preload] [--instantapp] [--full] [--dont-kill]");
+        pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
+        pw.println("    Install an application.  Must provide the apk data to install, either as a");
+        pw.println("    file path or '-' to read from stdin.  Options are:");
+        pw.println("      -l: forward lock application");
+        pw.println("      -r: allow replacement of existing application");
+        pw.println("      -t: allow test packages");
+        pw.println("      -i: specify package name of installer owning the app");
+        pw.println("      -s: install application on sdcard");
+        pw.println("      -f: install application on internal flash");
+        pw.println("      -d: allow version code downgrade (debuggable packages only)");
+        pw.println("      -p: partial application install (new split on top of existing pkg)");
+        pw.println("      -g: grant all runtime permissions");
+        pw.println("      -S: size in bytes of package, required for stdin");
+        pw.println("      --user: install under the given user.");
+        pw.println("      --dont-kill: installing a new feature split, don't kill running app");
+        pw.println("      --originating-uri: set URI where app was downloaded from");
+        pw.println("      --referrer: set URI that instigated the install of the app");
+        pw.println("      --pkg: specify expected package name of app being installed");
+        pw.println("      --abi: override the default ABI of the platform");
+        pw.println("      --instantapp: cause the app to be installed as an ephemeral install app");
+        pw.println("      --full: cause the app to be installed as a non-ephemeral full app");
+        pw.println("      --install-location: force the install location:");
+        pw.println("          0=auto, 1=internal only, 2=prefer external");
+        pw.println("      --force-uuid: force install on to disk volume with given UUID");
+        pw.println("      --force-sdk: allow install even when existing app targets platform");
+        pw.println("          codename but new one targets a final API level");
+        pw.println("");
+        pw.println("  install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]");
+        pw.println("       [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        pw.println("       [--originating-uri URI] [---referrer URI]");
+        pw.println("       [--abi ABI_NAME] [--force-sdk]");
+        pw.println("       [--preload] [--instantapp] [--full] [--dont-kill]");
+        pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
+        pw.println("    Like \"install\", but starts an install session.  Use \"install-write\"");
+        pw.println("    to push data into the session, and \"install-commit\" to finish.");
+        pw.println("");
+        pw.println("  install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
+        pw.println("    Write an apk into the given install session.  If the path is '-', data");
+        pw.println("    will be read from stdin.  Options are:");
+        pw.println("      -S: size in bytes of package, required for stdin");
+        pw.println("");
+        pw.println("  install-commit SESSION_ID");
+        pw.println("    Commit the given active install session, installing the app.");
+        pw.println("");
+        pw.println("  install-abandon SESSION_ID");
+        pw.println("    Delete the given active install session.");
+        pw.println("");
+        pw.println("  set-install-location LOCATION");
+        pw.println("    Changes the default install location.  NOTE this is only intended for debugging;");
+        pw.println("    using this can cause applications to break and other undersireable behavior.");
+        pw.println("    LOCATION is one of:");
+        pw.println("    0 [auto]: Let system decide the best location");
+        pw.println("    1 [internal]: Install on internal device storage");
+        pw.println("    2 [external]: Install on external media");
+        pw.println("");
+        pw.println("  get-install-location");
+        pw.println("    Returns the current install location: 0, 1 or 2 as per set-install-location.");
+        pw.println("");
+        pw.println("  move-package PACKAGE [internal|UUID]");
+        pw.println("");
+        pw.println("  move-primary-storage [internal|UUID]");
+        pw.println("");
+        pw.println("  pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE [SPLIT]");
+        pw.println("    Remove the given package name from the system.  May remove an entire app");
+        pw.println("    if no SPLIT name is specified, otherwise will remove only the split of the");
+        pw.println("    given app.  Options are:");
+        pw.println("      -k: keep the data and cache directories around after package removal.");
+        pw.println("      --user: remove the app from the given user.");
+        pw.println("      --versionCode: only uninstall if the app has the given version code.");
+        pw.println("");
+        pw.println("  clear [--user USER_ID] PACKAGE");
+        pw.println("    Deletes all data associated with a package.");
+        pw.println("");
+        pw.println("  enable [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  disable [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("    These commands change the enabled state of a given package or");
+        pw.println("    component (written as \"package/class\").");
+        pw.println("");
+        pw.println("  hide [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("");
+        pw.println("  suspend [--user USER_ID] TARGET-PACKAGE");
+        pw.println("    Suspends the specified package (as user).");
+        pw.println("");
+        pw.println("  unsuspend [--user USER_ID] TARGET-PACKAGE");
+        pw.println("    Unsuspends the specified package (as user).");
+        pw.println("");
+        pw.println("  grant [--user USER_ID] PACKAGE PERMISSION");
+        pw.println("  revoke [--user USER_ID] PACKAGE PERMISSION");
+        pw.println("    These commands either grant or revoke permissions to apps.  The permissions");
+        pw.println("    must be declared as used in the app's manifest, be runtime permissions");
+        pw.println("    (protection level dangerous), and the app targeting SDK greater than Lollipop MR1.");
+        pw.println("");
+        pw.println("  reset-permissions");
+        pw.println("    Revert all runtime permissions to their default state.");
+        pw.println("");
+        pw.println("  set-permission-enforced PERMISSION [true|false]");
+        pw.println("");
+        pw.println("  get-privapp-permissions TARGET-PACKAGE");
+        pw.println("    Prints all privileged permissions for a package.");
+        pw.println("");
+        pw.println("  get-privapp-deny-permissions TARGET-PACKAGE");
+        pw.println("    Prints all privileged permissions that are denied for a package.");
+        pw.println("");
+        pw.println("  get-oem-permissions TARGET-PACKAGE");
+        pw.println("    Prints all OEM permissions for a package.");
+        pw.println("");
+        pw.println("  set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
+        pw.println("  get-app-link [--user USER_ID] PACKAGE");
+        pw.println("");
+        pw.println("  trim-caches DESIRED_FREE_SPACE [internal|UUID]");
+        pw.println("    Trim cache files to reach the given free space.");
+        pw.println("");
+        pw.println("  create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]");
+        pw.println("      [--guest] USER_NAME");
+        pw.println("    Create a new user with the given USER_NAME, printing the new user identifier");
+        pw.println("    of the user.");
+        pw.println("");
+        pw.println("  remove-user USER_ID");
+        pw.println("    Remove the user with the given USER_IDENTIFIER, deleting all data");
+        pw.println("    associated with that user");
+        pw.println("");
+        pw.println("  set-user-restriction [--user USER_ID] RESTRICTION VALUE");
+        pw.println("");
+        pw.println("  get-max-users");
+        pw.println("");
         pw.println("  compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]");
         pw.println("          [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)");
-        pw.println("    Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".");
-        pw.println("    Options:");
+        pw.println("    Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".  Options are:");
         pw.println("      -a: compile all packages");
         pw.println("      -c: clear profile data before compiling");
         pw.println("      -f: force compilation even if not needed");
@@ -1690,72 +2568,32 @@
         pw.println("      --check-prof (true | false): look at profiles when doing dexopt?");
         pw.println("      --secondary-dex: compile app secondary dex files");
         pw.println("      --split SPLIT: compile only the given split name");
+        pw.println("");
+        pw.println("  force-dex-opt PACKAGE");
+        pw.println("    Force immediate execution of dex opt for the given PACKAGE.");
+        pw.println("");
         pw.println("  bg-dexopt-job");
         pw.println("    Execute the background optimizations immediately.");
         pw.println("    Note that the command only runs the background optimizer logic. It may");
         pw.println("    overlap with the actual job but the job scheduler will not be able to");
         pw.println("    cancel it. It will also run even if the device is not in the idle");
         pw.println("    maintenance mode.");
-        pw.println("  list features");
-        pw.println("    Prints all features of the system.");
-        pw.println("  list instrumentation [-f] [TARGET-PACKAGE]");
-        pw.println("    Prints all test packages; optionally only those targeting TARGET-PACKAGE");
-        pw.println("    Options:");
-        pw.println("      -f: dump the name of the .apk file containing the test package");
-        pw.println("  list libraries");
-        pw.println("    Prints all system libraries.");
-        pw.println("  list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] "
-                + "[--uid UID] [--user USER_ID] [FILTER]");
-        pw.println("    Prints all packages; optionally only those whose name contains");
-        pw.println("    the text in FILTER.");
-        pw.println("    Options:");
-        pw.println("      -f: see their associated file");
-        pw.println("      -d: filter to only show disabled packages");
-        pw.println("      -e: filter to only show enabled packages");
-        pw.println("      -s: filter to only show system packages");
-        pw.println("      -3: filter to only show third party packages");
-        pw.println("      -i: see the installer for the packages");
-        pw.println("      -l: ignored (used for compatibility with older releases)");
-        pw.println("      -U: also show the package UID");
-        pw.println("      -u: also include uninstalled packages");
-        pw.println("      --uid UID: filter to only show packages with the given UID");
-        pw.println("      --user USER_ID: only list packages belonging to the given user");
+        pw.println("");
         pw.println("  reconcile-secondary-dex-files TARGET-PACKAGE");
         pw.println("    Reconciles the package secondary dex files with the generated oat files.");
-        pw.println("  list permission-groups");
-        pw.println("    Prints all known permission groups.");
-        pw.println("  list permissions [-g] [-f] [-d] [-u] [GROUP]");
-        pw.println("    Prints all known permissions; optionally only those in GROUP.");
-        pw.println("    Options:");
-        pw.println("      -g: organize by group");
-        pw.println("      -f: print all information");
-        pw.println("      -s: short summary");
-        pw.println("      -d: only list dangerous permissions");
-        pw.println("      -u: list only the permissions users will see");
+        pw.println("");
         pw.println("  dump-profiles TARGET-PACKAGE");
         pw.println("    Dumps method/class profile files to");
         pw.println("    /data/misc/profman/TARGET-PACKAGE.txt");
-        pw.println("  resolve-activity [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints the activity that resolves to the given Intent.");
-        pw.println("  query-activities [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints all activities that can handle the given Intent.");
-        pw.println("  query-services [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints all services that can handle the given Intent.");
-        pw.println("  query-receivers [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints all broadcast receivers that can handle the given Intent.");
-        pw.println("  suspend [--user USER_ID] TARGET-PACKAGE");
-        pw.println("    Suspends the specified package (as user).");
-        pw.println("  unsuspend [--user USER_ID] TARGET-PACKAGE");
-        pw.println("    Unsuspends the specified package (as user).");
+        pw.println("");
         pw.println("  set-home-activity [--user USER_ID] TARGET-COMPONENT");
-        pw.println("    set the default home activity (aka launcher).");
-        pw.println("  has-feature FEATURE_NAME [version]");
-        pw.println("   prints true and returns exit status 0 when system has a FEATURE_NAME,");
-        pw.println("   otherwise prints false and returns exit status 1");
-        pw.println("  get-privileged-permissions TARGET-PACKAGE");
-        pw.println("   prints all privileged permissions for a package.");
-        pw.println("  get-oem-permissions TARGET-PACKAGE");
-        pw.println("   prints all OEM permissions for a package.");
+        pw.println("    Set the default home activity (aka launcher).");
+        pw.println("");
+        pw.println("  set-installer PACKAGE INSTALLER");
+        pw.println("    Set installer package name");
+        pw.println("");
+        pw.println("  get-instantapp-resolver");
+        pw.println("    Return the name of the component that is the current instant app installer.");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
diff --git a/com/android/server/pm/PackageSetting.java b/com/android/server/pm/PackageSetting.java
index 83cb2db..3b414e9 100644
--- a/com/android/server/pm/PackageSetting.java
+++ b/com/android/server/pm/PackageSetting.java
@@ -86,6 +86,10 @@
         return sharedUserId;
     }
 
+    public SharedUserSetting getSharedUser() {
+        return sharedUser;
+    }
+
     @Override
     public String toString() {
         return "PackageSetting{"
@@ -120,6 +124,14 @@
         return appId;
     }
 
+    public void setInstallPermissionsFixed(boolean fixed) {
+        installPermissionsFixed = fixed;
+    }
+
+    public boolean areInstallPermissionsFixed() {
+        return installPermissionsFixed;
+    }
+
     public boolean isPrivileged() {
         return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
@@ -136,6 +148,10 @@
         return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
     }
 
+    public boolean isUpdatedSystem() {
+        return (pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
     @Override
     public boolean isSharedUser() {
         return sharedUser != null;
diff --git a/com/android/server/pm/PackageSettingBase.java b/com/android/server/pm/PackageSettingBase.java
index e19e83f..a838768 100644
--- a/com/android/server/pm/PackageSettingBase.java
+++ b/com/android/server/pm/PackageSettingBase.java
@@ -24,6 +24,7 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageUserState;
+import android.content.pm.Signature;
 import android.service.pm.PackageProto;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -57,7 +58,7 @@
     static final int PKG_INSTALL_COMPLETE = 1;
     static final int PKG_INSTALL_INCOMPLETE = 0;
 
-    final String name;
+    public final String name;
     final String realName;
 
     String parentPackageName;
@@ -231,6 +232,11 @@
     public boolean isSharedUser() {
         return false;
     }
+
+    public Signature[] getSignatures() {
+        return signatures.mSignatures;
+    }
+
     /**
      * Makes a shallow copy of the given package settings.
      *
diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java
index 191b43a..7077fd1 100644
--- a/com/android/server/pm/Settings.java
+++ b/com/android/server/pm/Settings.java
@@ -544,7 +544,7 @@
         }
         final PackageSetting dp = mDisabledSysPackages.get(name);
         // always make sure the system package code and resource paths dont change
-        if (dp == null && p.pkg != null && p.pkg.isSystemApp() && !p.pkg.isUpdatedSystemApp()) {
+        if (dp == null && p.pkg != null && p.pkg.isSystem() && !p.pkg.isUpdatedSystemApp()) {
             if((p.pkg != null) && (p.pkg.applicationInfo != null)) {
                 p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
             }
diff --git a/com/android/server/pm/UserDataPreparer.java b/com/android/server/pm/UserDataPreparer.java
index b8b00af..bfe09b8 100644
--- a/com/android/server/pm/UserDataPreparer.java
+++ b/com/android/server/pm/UserDataPreparer.java
@@ -132,11 +132,9 @@
                 if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
                     FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
                     FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
-                    FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId));
                 }
                 if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
                     FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
-                    FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId));
                 }
             }
 
diff --git a/com/android/server/pm/UserRestrictionsUtils.java b/com/android/server/pm/UserRestrictionsUtils.java
index c18a71d..c86122f 100644
--- a/com/android/server/pm/UserRestrictionsUtils.java
+++ b/com/android/server/pm/UserRestrictionsUtils.java
@@ -80,6 +80,7 @@
             UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
             UserManager.DISALLOW_DEBUGGING_FEATURES,
             UserManager.DISALLOW_CONFIG_VPN,
+            UserManager.DISALLOW_CONFIG_DATE_TIME,
             UserManager.DISALLOW_CONFIG_TETHERING,
             UserManager.DISALLOW_NETWORK_RESET,
             UserManager.DISALLOW_FACTORY_RESET,
@@ -157,6 +158,7 @@
     private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
             UserManager.DISALLOW_ADJUST_VOLUME,
             UserManager.DISALLOW_BLUETOOTH_SHARING,
+            UserManager.DISALLOW_CONFIG_DATE_TIME,
             UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
             UserManager.DISALLOW_RUN_IN_BACKGROUND,
             UserManager.DISALLOW_UNMUTE_MICROPHONE,
diff --git a/com/android/server/pm/dex/DexoptOptions.java b/com/android/server/pm/dex/DexoptOptions.java
index 4fa47b5..0966770 100644
--- a/com/android/server/pm/dex/DexoptOptions.java
+++ b/com/android/server/pm/dex/DexoptOptions.java
@@ -56,6 +56,9 @@
     // actually shared at runtime.
     public static final int DEXOPT_AS_SHARED_LIBRARY = 1 << 6;
 
+    // When set, indicates that dexopt is invoked from the background service.
+    public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
+
     // The name of package to optimize.
     private final String mPackageName;
 
@@ -86,7 +89,8 @@
                 DEXOPT_ONLY_SECONDARY_DEX |
                 DEXOPT_ONLY_SHARED_DEX |
                 DEXOPT_DOWNGRADE |
-                DEXOPT_AS_SHARED_LIBRARY;
+                DEXOPT_AS_SHARED_LIBRARY |
+                DEXOPT_IDLE_BACKGROUND_JOB;
         if ((flags & (~validityMask)) != 0) {
             throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags));
         }
@@ -133,6 +137,10 @@
         return (mFlags & DEXOPT_AS_SHARED_LIBRARY) != 0;
     }
 
+    public boolean isDexoptIdleBackgroundJob() {
+        return (mFlags & DEXOPT_IDLE_BACKGROUND_JOB) != 0;
+    }
+
     public String getSplitName() {
         return mSplitName;
     }
diff --git a/com/android/server/pm/permission/BasePermission.java b/com/android/server/pm/permission/BasePermission.java
index 71d3202..8c86db6 100644
--- a/com/android/server/pm/permission/BasePermission.java
+++ b/com/android/server/pm/permission/BasePermission.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.Permission;
 import android.content.pm.PermissionInfo;
+import android.content.pm.Signature;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
@@ -129,6 +130,9 @@
     public PackageSettingBase getSourcePackageSetting() {
         return sourcePackageSetting;
     }
+    public Signature[] getSourceSignatures() {
+        return sourcePackageSetting.getSignatures();
+    }
     public int getType() {
         return type;
     }
@@ -277,8 +281,8 @@
         // Allow system apps to redefine non-system permissions
         if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) {
             final boolean currentOwnerIsSystem = (bp.perm != null
-                    && bp.perm.owner.isSystemApp());
-            if (p.owner.isSystemApp()) {
+                    && bp.perm.owner.isSystem());
+            if (p.owner.isSystem()) {
                 if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
                     // It's a built-in permission and no owner, take ownership now
                     bp.sourcePackageSetting = pkgSetting;
diff --git a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 161efd3..533b261 100644
--- a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -985,7 +985,7 @@
 
     private PackageParser.Package getSystemPackage(String packageName) {
         PackageParser.Package pkg = getPackage(packageName);
-        if (pkg != null && pkg.isSystemApp()) {
+        if (pkg != null && pkg.isSystem()) {
             return !isSysComponentOrPersistentPlatformSignedPrivApp(pkg) ? pkg : null;
         }
         return null;
@@ -1094,7 +1094,7 @@
         if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
             return true;
         }
-        if (!pkg.isPrivilegedApp()) {
+        if (!pkg.isPrivileged()) {
             return false;
         }
         final PackageParser.Package disabledPkg =
diff --git a/com/android/server/pm/permission/PermissionManagerInternal.java b/com/android/server/pm/permission/PermissionManagerInternal.java
index 8aac52a..60c118b 100644
--- a/com/android/server/pm/permission/PermissionManagerInternal.java
+++ b/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.PackageParser;
+import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManager.PermissionInfoFlags;
@@ -57,7 +58,7 @@
         }
         public void onInstallPermissionRevoked() {
         }
-        public void onPermissionUpdated(int userId) {
+        public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
         }
         public void onPermissionRemoved() {
         }
@@ -65,6 +66,10 @@
         }
     }
 
+    public abstract void systemReady();
+
+    public abstract boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId);
+
     public abstract void grantRuntimePermission(
             @NonNull String permName, @NonNull String packageName, boolean overridePolicy,
             int callingUid, int userId, @Nullable PermissionCallback callback);
@@ -78,9 +83,12 @@
     public abstract void revokeRuntimePermission(@NonNull String permName,
             @NonNull String packageName, boolean overridePolicy, int callingUid, int userId,
             @Nullable PermissionCallback callback);
-    public abstract int[] revokeUnusedSharedUserPermissions(@NonNull SharedUserSetting suSetting,
-            @NonNull int[] allUserIds);
 
+    public abstract void updatePermissions(@Nullable String packageName,
+            @Nullable PackageParser.Package pkg, boolean replaceGrant,
+            @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback);
+    public abstract void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdated,
+            @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback);
 
     /**
      * Add all permissions in the given package.
@@ -89,22 +97,28 @@
      * the permission settings.
      */
     public abstract void addAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
+    public abstract void addAllPermissionGroups(@NonNull PackageParser.Package pkg, boolean chatty);
     public abstract void removeAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
     public abstract boolean addDynamicPermission(@NonNull PermissionInfo info, boolean async,
             int callingUid, @Nullable PermissionCallback callback);
     public abstract void removeDynamicPermission(@NonNull String permName, int callingUid,
             @Nullable PermissionCallback callback);
 
-    public abstract int updatePermissions(@Nullable String changingPkg,
-            @Nullable PackageParser.Package pkgInfo, int flags);
-    public abstract int updatePermissionTrees(@Nullable String changingPkg,
-            @Nullable PackageParser.Package pkgInfo, int flags);
-
     public abstract @Nullable String[] getAppOpPermissionPackages(@NonNull String permName);
 
     public abstract int getPermissionFlags(@NonNull String permName,
             @NonNull String packageName, int callingUid, int userId);
     /**
+     * Retrieve all of the information we know about a particular group of permissions.
+     */
+    public abstract @Nullable PermissionGroupInfo getPermissionGroupInfo(
+            @NonNull String groupName, int flags, int callingUid);
+    /**
+     * Retrieve all of the known permission groups in the system.
+     */
+    public abstract @Nullable List<PermissionGroupInfo> getAllPermissionGroups(int flags,
+            int callingUid);
+    /**
      * Retrieve all of the information we know about a particular permission.
      */
     public abstract @Nullable PermissionInfo getPermissionInfo(@NonNull String permName,
@@ -132,6 +146,7 @@
 
     public abstract int checkPermission(@NonNull String permName, @NonNull String packageName,
             int callingUid, int userId);
+    public abstract int checkUidPermission(String permName, int uid, int callingUid);
 
     /**
      * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
@@ -147,8 +162,5 @@
     public abstract @NonNull DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy();
 
     /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
-    public abstract Iterator<BasePermission> getPermissionIteratorTEMP();
     public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
-    public abstract void putPermissionTEMP(@NonNull String permName,
-            @NonNull BasePermission permission);
 }
\ No newline at end of file
diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java
index d2d857c..76805ce 100644
--- a/com/android/server/pm/permission/PermissionManagerService.java
+++ b/com/android/server/pm/permission/PermissionManagerService.java
@@ -18,6 +18,13 @@
 
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
+import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
+import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -27,7 +34,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
-import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.PackageParser.Package;
 import android.os.Binder;
@@ -35,18 +42,24 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
+import android.os.storage.StorageManager;
 import android.os.storage.StorageManagerInternal;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.RoSystemProperties;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
@@ -58,6 +71,7 @@
 import com.android.server.pm.PackageSetting;
 import com.android.server.pm.ProcessLoggingHandler;
 import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.UserManagerService;
 import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
 import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
 import com.android.server.pm.permission.PermissionsState.PermissionState;
@@ -69,6 +83,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -107,8 +122,19 @@
             Manifest.permission.READ_PHONE_NUMBERS,
             Manifest.permission.ANSWER_PHONE_CALLS);
 
-    /** Cap the size of permission trees that 3rd party apps can define */
-    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;     // characters of text
+    /** Permission grant: not grant the permission. */
+    private static final int GRANT_DENIED = 1;
+    /** Permission grant: grant the permission as an install permission. */
+    private static final int GRANT_INSTALL = 2;
+    /** Permission grant: grant the permission as a runtime one. */
+    private static final int GRANT_RUNTIME = 3;
+    /** Permission grant: grant as runtime a permission that was granted as an install time one. */
+    private static final int GRANT_UPGRADE = 4;
+
+    /** Cap the size of permission trees that 3rd party apps can define; in characters of text */
+    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;
+    /** Empty array to avoid allocations */
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
 
     /** Lock to protect internal data access */
     private final Object mLock;
@@ -122,13 +148,29 @@
     /** Default permission policy to provide proper behaviour out-of-the-box */
     private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
 
-    /** Internal storage for permissions and related settings */
-    private final PermissionSettings mSettings;
+    /**
+     * Built-in permissions. Read from system configuration files. Mapping is from
+     * UID to permission name.
+     */
+    private final SparseArray<ArraySet<String>> mSystemPermissions;
+
+    /** Built-in group IDs given to all packages. Read from system configuration files. */
+    private final int[] mGlobalGids;
 
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
     private final Context mContext;
 
+    /** Internal storage for permissions and related settings */
+    @GuardedBy("mLock")
+    private final PermissionSettings mSettings;
+
+    @GuardedBy("mLock")
+    private ArraySet<String> mPrivappPermissionsViolations;
+
+    @GuardedBy("mLock")
+    private boolean mSystemReady;
+
     PermissionManagerService(Context context,
             @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
             @NonNull Object externalLock) {
@@ -146,6 +188,9 @@
 
         mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy(
                 context, mHandlerThread.getLooper(), defaultGrantCallback, this);
+        SystemConfig systemConfig = SystemConfig.getInstance();
+        mSystemPermissions = systemConfig.getSystemPermissions();
+        mGlobalGids = systemConfig.getGlobalGids();
 
         // propagate permission configuration
         final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
@@ -230,14 +275,105 @@
         return PackageManager.PERMISSION_DENIED;
     }
 
-    private PermissionInfo getPermissionInfo(String name, String packageName, int flags,
+    private int checkUidPermission(String permName, int uid, int callingUid) {
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        final boolean isCallerInstantApp =
+                mPackageManagerInt.getInstantAppPackageName(callingUid) != null;
+        final boolean isUidInstantApp =
+                mPackageManagerInt.getInstantAppPackageName(uid) != null;
+        final int userId = UserHandle.getUserId(uid);
+        if (!mUserManagerInt.exists(userId)) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+
+        final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+        if (packages != null && packages.length > 0) {
+            PackageParser.Package pkg = null;
+            for (String packageName : packages) {
+                pkg = mPackageManagerInt.getPackage(packageName);
+                if (pkg != null) {
+                    break;
+                }
+            }
+            if (pkg == null) {
+Slog.e(TAG, "TODD: No package not found; UID: " + uid);
+Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages));
+                return PackageManager.PERMISSION_DENIED;
+            }
+            if (pkg.mSharedUserId != null) {
+                if (isCallerInstantApp) {
+                    return PackageManager.PERMISSION_DENIED;
+                }
+            } else {
+                if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) {
+                    return PackageManager.PERMISSION_DENIED;
+                }
+            }
+            final PermissionsState permissionsState =
+                    ((PackageSetting) pkg.mExtras).getPermissionsState();
+            if (permissionsState.hasPermission(permName, userId)) {
+                if (isUidInstantApp) {
+                    if (mSettings.isPermissionInstant(permName)) {
+                        return PackageManager.PERMISSION_GRANTED;
+                    }
+                } else {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+            // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+            if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+                    .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+        } else {
+            ArraySet<String> perms = mSystemPermissions.get(uid);
+            if (perms != null) {
+                if (perms.contains(permName)) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
+                        .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+        }
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    private PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags,
+            int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        synchronized (mLock) {
+            return PackageParser.generatePermissionGroupInfo(
+                    mSettings.mPermissionGroups.get(groupName), flags);
+        }
+    }
+
+    private List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        synchronized (mLock) {
+            final int N = mSettings.mPermissionGroups.size();
+            final ArrayList<PermissionGroupInfo> out
+                    = new ArrayList<PermissionGroupInfo>(N);
+            for (PackageParser.PermissionGroup pg : mSettings.mPermissionGroups.values()) {
+                out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
+            }
+            return out;
+        }
+    }
+
+    private PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
             int callingUid) {
         if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
             return null;
         }
         // reader
         synchronized (mLock) {
-            final BasePermission bp = mSettings.getPermissionLocked(name);
+            final BasePermission bp = mSettings.getPermissionLocked(permName);
             if (bp == null) {
                 return null;
             }
@@ -252,14 +388,10 @@
         if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
             return null;
         }
-        // reader
         synchronized (mLock) {
-            // TODO Uncomment when mPermissionGroups moves to this class
-//            if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
-//                // This is thrown as NameNotFoundException
-//                return null;
-//            }
-
+            if (groupName != null && !mSettings.mPermissionGroups.containsKey(groupName)) {
+                return null;
+            }
             final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
             for (BasePermission bp : mSettings.mPermissions.values()) {
                 final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
@@ -314,21 +446,21 @@
             // Assume by default that we did not install this permission into the system.
             p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
 
-            // Now that permission groups have a special meaning, we ignore permission
-            // groups for legacy apps to prevent unexpected behavior. In particular,
-            // permissions for one app being granted to someone just because they happen
-            // to be in a group defined by another app (before this had no implications).
-            if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
-                p.group = mPackageManagerInt.getPermissionGroupTEMP(p.info.group);
-                // Warn for a permission in an unknown group.
-                if (PackageManagerService.DEBUG_PERMISSIONS
-                        && p.info.group != null && p.group == null) {
-                    Slog.i(TAG, "Permission " + p.info.name + " from package "
-                            + p.info.packageName + " in an unknown group " + p.info.group);
-                }
-            }
-
             synchronized (PermissionManagerService.this.mLock) {
+                // Now that permission groups have a special meaning, we ignore permission
+                // groups for legacy apps to prevent unexpected behavior. In particular,
+                // permissions for one app being granted to someone just because they happen
+                // to be in a group defined by another app (before this had no implications).
+                if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+                    p.group = mSettings.mPermissionGroups.get(p.info.group);
+                    // Warn for a permission in an unknown group.
+                    if (DEBUG_PERMISSIONS
+                            && p.info.group != null && p.group == null) {
+                        Slog.i(TAG, "Permission " + p.info.name + " from package "
+                                + p.info.packageName + " in an unknown group " + p.info.group);
+                    }
+                }
+
                 if (p.tree) {
                     final BasePermission bp = BasePermission.createOrUpdate(
                             mSettings.getPermissionTreeLocked(p.info.name), p, pkg,
@@ -344,6 +476,48 @@
         }
     }
 
+    private void addAllPermissionGroups(PackageParser.Package pkg, boolean chatty) {
+        final int N = pkg.permissionGroups.size();
+        StringBuilder r = null;
+        for (int i=0; i<N; i++) {
+            final PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
+            final PackageParser.PermissionGroup cur = mSettings.mPermissionGroups.get(pg.info.name);
+            final String curPackageName = (cur == null) ? null : cur.info.packageName;
+            final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
+            if (cur == null || isPackageUpdate) {
+                mSettings.mPermissionGroups.put(pg.info.name, pg);
+                if (chatty && DEBUG_PACKAGE_SCANNING) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    if (isPackageUpdate) {
+                        r.append("UPD:");
+                    }
+                    r.append(pg.info.name);
+                }
+            } else {
+                Slog.w(TAG, "Permission group " + pg.info.name + " from package "
+                        + pg.info.packageName + " ignored: original from "
+                        + cur.info.packageName);
+                if (chatty && DEBUG_PACKAGE_SCANNING) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append("DUP:");
+                    r.append(pg.info.name);
+                }
+            }
+        }
+        if (r != null && DEBUG_PACKAGE_SCANNING) {
+            Log.d(TAG, "  Permission Groups: " + r);
+        }
+
+    }
+
     private void removeAllPermissions(PackageParser.Package pkg, boolean chatty) {
         synchronized (mLock) {
             int N = pkg.permissions.size();
@@ -356,7 +530,7 @@
                 }
                 if (bp != null && bp.isPermission(p)) {
                     bp.setPermission(null);
-                    if (PackageManagerService.DEBUG_REMOVE && chatty) {
+                    if (DEBUG_REMOVE && chatty) {
                         if (r == null) {
                             r = new StringBuilder(256);
                         } else {
@@ -374,7 +548,7 @@
                 }
             }
             if (r != null) {
-                if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
             }
 
             N = pkg.requestedPermissions.size();
@@ -392,7 +566,7 @@
                 }
             }
             if (r != null) {
-                if (PackageManagerService.DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
             }
         }
     }
@@ -455,6 +629,581 @@
         }
     }
 
+    private void grantPermissions(PackageParser.Package pkg, boolean replace,
+            String packageOfInterest, PermissionCallback callback) {
+        // IMPORTANT: There are two types of permissions: install and runtime.
+        // Install time permissions are granted when the app is installed to
+        // all device users and users added in the future. Runtime permissions
+        // are granted at runtime explicitly to specific users. Normal and signature
+        // protected permissions are install time permissions. Dangerous permissions
+        // are install permissions if the app's target SDK is Lollipop MR1 or older,
+        // otherwise they are runtime permissions. This function does not manage
+        // runtime permissions except for the case an app targeting Lollipop MR1
+        // being upgraded to target a newer SDK, in which case dangerous permissions
+        // are transformed from install time to runtime ones.
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return;
+        }
+        final boolean isLegacySystemApp = mPackageManagerInt.isLegacySystemApp(pkg);
+
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        PermissionsState origPermissions = permissionsState;
+
+        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+
+        boolean runtimePermissionsRevoked = false;
+        int[] updatedUserIds = EMPTY_INT_ARRAY;
+
+        boolean changedInstallPermission = false;
+
+        if (replace) {
+            ps.setInstallPermissionsFixed(false);
+            if (!ps.isSharedUser()) {
+                origPermissions = new PermissionsState(permissionsState);
+                permissionsState.reset();
+            } else {
+                // We need to know only about runtime permission changes since the
+                // calling code always writes the install permissions state but
+                // the runtime ones are written only if changed. The only cases of
+                // changed runtime permissions here are promotion of an install to
+                // runtime and revocation of a runtime from a shared user.
+                synchronized (mLock) {
+                    updatedUserIds = revokeUnusedSharedUserPermissionsLocked(
+                            ps.getSharedUser(), UserManagerService.getInstance().getUserIds());
+                    if (!ArrayUtils.isEmpty(updatedUserIds)) {
+                        runtimePermissionsRevoked = true;
+                    }
+                }
+            }
+        }
+
+        permissionsState.setGlobalGids(mGlobalGids);
+
+        synchronized (mLock) {
+            final int N = pkg.requestedPermissions.size();
+            for (int i = 0; i < N; i++) {
+                final String permName = pkg.requestedPermissions.get(i);
+                final BasePermission bp = mSettings.getPermissionLocked(permName);
+                final boolean appSupportsRuntimePermissions =
+                        pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
+
+                if (DEBUG_INSTALL) {
+                    Log.i(TAG, "Package " + pkg.packageName + " checking " + permName + ": " + bp);
+                }
+
+                if (bp == null || bp.getSourcePackageSetting() == null) {
+                    if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
+                        if (DEBUG_PERMISSIONS) {
+                            Slog.i(TAG, "Unknown permission " + permName
+                                    + " in package " + pkg.packageName);
+                        }
+                    }
+                    continue;
+                }
+
+                // Limit ephemeral apps to ephemeral allowed permissions.
+                if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
+                    if (DEBUG_PERMISSIONS) {
+                        Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
+                                + " for package " + pkg.packageName);
+                    }
+                    continue;
+                }
+
+                if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
+                    if (DEBUG_PERMISSIONS) {
+                        Log.i(TAG, "Denying runtime-only permission " + bp.getName()
+                                + " for package " + pkg.packageName);
+                    }
+                    continue;
+                }
+
+                final String perm = bp.getName();
+                boolean allowedSig = false;
+                int grant = GRANT_DENIED;
+
+                // Keep track of app op permissions.
+                if (bp.isAppOp()) {
+                    mSettings.addAppOpPackage(perm, pkg.packageName);
+                }
+
+                if (bp.isNormal()) {
+                    // For all apps normal permissions are install time ones.
+                    grant = GRANT_INSTALL;
+                } else if (bp.isRuntime()) {
+                    // If a permission review is required for legacy apps we represent
+                    // their permissions as always granted runtime ones since we need
+                    // to keep the review required permission flag per user while an
+                    // install permission's state is shared across all users.
+                    if (!appSupportsRuntimePermissions && !mSettings.mPermissionReviewRequired) {
+                        // For legacy apps dangerous permissions are install time ones.
+                        grant = GRANT_INSTALL;
+                    } else if (origPermissions.hasInstallPermission(bp.getName())) {
+                        // For legacy apps that became modern, install becomes runtime.
+                        grant = GRANT_UPGRADE;
+                    } else if (isLegacySystemApp) {
+                        // For legacy system apps, install becomes runtime.
+                        // We cannot check hasInstallPermission() for system apps since those
+                        // permissions were granted implicitly and not persisted pre-M.
+                        grant = GRANT_UPGRADE;
+                    } else {
+                        // For modern apps keep runtime permissions unchanged.
+                        grant = GRANT_RUNTIME;
+                    }
+                } else if (bp.isSignature()) {
+                    // For all apps signature permissions are install time ones.
+                    allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
+                    if (allowedSig) {
+                        grant = GRANT_INSTALL;
+                    }
+                }
+
+                if (DEBUG_PERMISSIONS) {
+                    Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName);
+                }
+
+                if (grant != GRANT_DENIED) {
+                    if (!ps.isSystem() && ps.areInstallPermissionsFixed()) {
+                        // If this is an existing, non-system package, then
+                        // we can't add any new permissions to it.
+                        if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
+                            // Except...  if this is a permission that was added
+                            // to the platform (note: need to only do this when
+                            // updating the platform).
+                            if (!isNewPlatformPermissionForPackage(perm, pkg)) {
+                                grant = GRANT_DENIED;
+                            }
+                        }
+                    }
+
+                    switch (grant) {
+                        case GRANT_INSTALL: {
+                            // Revoke this as runtime permission to handle the case of
+                            // a runtime permission being downgraded to an install one.
+                            // Also in permission review mode we keep dangerous permissions
+                            // for legacy apps
+                            for (int userId : UserManagerService.getInstance().getUserIds()) {
+                                if (origPermissions.getRuntimePermissionState(
+                                        perm, userId) != null) {
+                                    // Revoke the runtime permission and clear the flags.
+                                    origPermissions.revokeRuntimePermission(bp, userId);
+                                    origPermissions.updatePermissionFlags(bp, userId,
+                                          PackageManager.MASK_PERMISSION_FLAGS, 0);
+                                    // If we revoked a permission permission, we have to write.
+                                    updatedUserIds = ArrayUtils.appendInt(
+                                            updatedUserIds, userId);
+                                }
+                            }
+                            // Grant an install permission.
+                            if (permissionsState.grantInstallPermission(bp) !=
+                                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                changedInstallPermission = true;
+                            }
+                        } break;
+
+                        case GRANT_RUNTIME: {
+                            // Grant previously granted runtime permissions.
+                            for (int userId : UserManagerService.getInstance().getUserIds()) {
+                                final PermissionState permissionState = origPermissions
+                                        .getRuntimePermissionState(perm, userId);
+                                int flags = permissionState != null
+                                        ? permissionState.getFlags() : 0;
+                                if (origPermissions.hasRuntimePermission(perm, userId)) {
+                                    // Don't propagate the permission in a permission review
+                                    // mode if the former was revoked, i.e. marked to not
+                                    // propagate on upgrade. Note that in a permission review
+                                    // mode install permissions are represented as constantly
+                                    // granted runtime ones since we need to keep a per user
+                                    // state associated with the permission. Also the revoke
+                                    // on upgrade flag is no longer applicable and is reset.
+                                    final boolean revokeOnUpgrade = (flags & PackageManager
+                                            .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+                                    if (revokeOnUpgrade) {
+                                        flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                        // Since we changed the flags, we have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                    if (!mSettings.mPermissionReviewRequired || !revokeOnUpgrade) {
+                                        if (permissionsState.grantRuntimePermission(bp, userId) ==
+                                                PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                            // If we cannot put the permission as it was,
+                                            // we have to write.
+                                            updatedUserIds = ArrayUtils.appendInt(
+                                                    updatedUserIds, userId);
+                                        }
+                                    }
+
+                                    // If the app supports runtime permissions no need for a review.
+                                    if (mSettings.mPermissionReviewRequired
+                                            && appSupportsRuntimePermissions
+                                            && (flags & PackageManager
+                                                    .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                                        flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+                                        // Since we changed the flags, we have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                } else if (mSettings.mPermissionReviewRequired
+                                        && !appSupportsRuntimePermissions) {
+                                    // For legacy apps that need a permission review, every new
+                                    // runtime permission is granted but it is pending a review.
+                                    // We also need to review only platform defined runtime
+                                    // permissions as these are the only ones the platform knows
+                                    // how to disable the API to simulate revocation as legacy
+                                    // apps don't expect to run with revoked permissions.
+                                    if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
+                                        if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+                                            flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
+                                            // We changed the flags, hence have to write.
+                                            updatedUserIds = ArrayUtils.appendInt(
+                                                    updatedUserIds, userId);
+                                        }
+                                    }
+                                    if (permissionsState.grantRuntimePermission(bp, userId)
+                                            != PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                        // We changed the permission, hence have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                }
+                                // Propagate the permission flags.
+                                permissionsState.updatePermissionFlags(bp, userId, flags, flags);
+                            }
+                        } break;
+
+                        case GRANT_UPGRADE: {
+                            // Grant runtime permissions for a previously held install permission.
+                            final PermissionState permissionState = origPermissions
+                                    .getInstallPermissionState(perm);
+                            final int flags =
+                                    (permissionState != null) ? permissionState.getFlags() : 0;
+
+                            if (origPermissions.revokeInstallPermission(bp)
+                                    != PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                // We will be transferring the permission flags, so clear them.
+                                origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                                        PackageManager.MASK_PERMISSION_FLAGS, 0);
+                                changedInstallPermission = true;
+                            }
+
+                            // If the permission is not to be promoted to runtime we ignore it and
+                            // also its other flags as they are not applicable to install permissions.
+                            if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {
+                                for (int userId : currentUserIds) {
+                                    if (permissionsState.grantRuntimePermission(bp, userId) !=
+                                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                        // Transfer the permission flags.
+                                        permissionsState.updatePermissionFlags(bp, userId,
+                                                flags, flags);
+                                        // If we granted the permission, we have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                }
+                            }
+                        } break;
+
+                        default: {
+                            if (packageOfInterest == null
+                                    || packageOfInterest.equals(pkg.packageName)) {
+                                if (DEBUG_PERMISSIONS) {
+                                    Slog.i(TAG, "Not granting permission " + perm
+                                            + " to package " + pkg.packageName
+                                            + " because it was previously installed without");
+                                }
+                            }
+                        } break;
+                    }
+                } else {
+                    if (permissionsState.revokeInstallPermission(bp) !=
+                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                        // Also drop the permission flags.
+                        permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                                PackageManager.MASK_PERMISSION_FLAGS, 0);
+                        changedInstallPermission = true;
+                        Slog.i(TAG, "Un-granting permission " + perm
+                                + " from package " + pkg.packageName
+                                + " (protectionLevel=" + bp.getProtectionLevel()
+                                + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+                                + ")");
+                    } else if (bp.isAppOp()) {
+                        // Don't print warning for app op permissions, since it is fine for them
+                        // not to be granted, there is a UI for the user to decide.
+                        if (DEBUG_PERMISSIONS
+                                && (packageOfInterest == null
+                                        || packageOfInterest.equals(pkg.packageName))) {
+                            Slog.i(TAG, "Not granting permission " + perm
+                                    + " to package " + pkg.packageName
+                                    + " (protectionLevel=" + bp.getProtectionLevel()
+                                    + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+                                    + ")");
+                        }
+                    }
+                }
+            }
+
+            if ((changedInstallPermission || replace) && !ps.areInstallPermissionsFixed() &&
+                    !ps.isSystem() || ps.isUpdatedSystem()) {
+                // This is the first that we have heard about this package, so the
+                // permissions we have now selected are fixed until explicitly
+                // changed.
+                ps.setInstallPermissionsFixed(true);
+            }
+        }
+
+        // Persist the runtime permissions state for users with changes. If permissions
+        // were revoked because no app in the shared user declares them we have to
+        // write synchronously to avoid losing runtime permissions state.
+        if (callback != null) {
+            callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked);
+        }
+    }
+
+    private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
+        boolean allowed = false;
+        final int NP = PackageParser.NEW_PERMISSIONS.length;
+        for (int ip=0; ip<NP; ip++) {
+            final PackageParser.NewPermissionInfo npi
+                    = PackageParser.NEW_PERMISSIONS[ip];
+            if (npi.name.equals(perm)
+                    && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
+                allowed = true;
+                Log.i(TAG, "Auto-granting " + perm + " to old pkg "
+                        + pkg.packageName);
+                break;
+            }
+        }
+        return allowed;
+    }
+
+    /**
+     * Determines whether a package is whitelisted for a particular privapp permission.
+     *
+     * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted.
+     *
+     * <p>This handles parent/child apps.
+     */
+    private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) {
+        ArraySet<String> wlPermissions = SystemConfig.getInstance()
+                .getPrivAppPermissions(pkg.packageName);
+        // Let's check if this package is whitelisted...
+        boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm);
+        // If it's not, we'll also tail-recurse to the parent.
+        return whitelisted ||
+                pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage);
+    }
+
+    private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
+            BasePermission bp, PermissionsState origPermissions) {
+        boolean oemPermission = bp.isOEM();
+        boolean privilegedPermission = bp.isPrivileged();
+        boolean privappPermissionsDisable =
+                RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
+        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
+        boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
+        if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivileged()
+                && !platformPackage && platformPermission) {
+            if (!hasPrivappWhitelistEntry(perm, pkg)) {
+                // Only report violations for apps on system image
+                if (!mSystemReady && !pkg.isUpdatedSystemApp()) {
+                    // it's only a reportable violation if the permission isn't explicitly denied
+                    final ArraySet<String> deniedPermissions = SystemConfig.getInstance()
+                            .getPrivAppDenyPermissions(pkg.packageName);
+                    final boolean permissionViolation =
+                            deniedPermissions == null || !deniedPermissions.contains(perm);
+                    if (permissionViolation) {
+                        Slog.w(TAG, "Privileged permission " + perm + " for package "
+                                + pkg.packageName + " - not in privapp-permissions whitelist");
+
+                        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                            if (mPrivappPermissionsViolations == null) {
+                                mPrivappPermissionsViolations = new ArraySet<>();
+                            }
+                            mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm);
+                        }
+                    } else {
+                        return false;
+                    }
+                }
+                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                    return false;
+                }
+            }
+        }
+        final String systemPackageName = mPackageManagerInt.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+        final PackageParser.Package systemPackage =
+                mPackageManagerInt.getPackage(systemPackageName);
+        boolean allowed = (PackageManagerService.compareSignatures(
+                                bp.getSourceSignatures(), pkg.mSignatures)
+                        == PackageManager.SIGNATURE_MATCH)
+                || (PackageManagerService.compareSignatures(
+                                systemPackage.mSignatures, pkg.mSignatures)
+                        == PackageManager.SIGNATURE_MATCH);
+        if (!allowed && (privilegedPermission || oemPermission)) {
+            if (pkg.isSystem()) {
+                // For updated system applications, a privileged/oem permission
+                // is granted only if it had been defined by the original application.
+                if (pkg.isUpdatedSystemApp()) {
+                    final PackageParser.Package disabledPkg =
+                            mPackageManagerInt.getDisabledPackage(pkg.packageName);
+                    final PackageSetting disabledPs =
+                            (disabledPkg != null) ? (PackageSetting) disabledPkg.mExtras : null;
+                    if (disabledPs != null
+                            && disabledPs.getPermissionsState().hasInstallPermission(perm)) {
+                        // If the original was granted this permission, we take
+                        // that grant decision as read and propagate it to the
+                        // update.
+                        if ((privilegedPermission && disabledPs.isPrivileged())
+                                || (oemPermission && disabledPs.isOem()
+                                        && canGrantOemPermission(disabledPs, perm))) {
+                            allowed = true;
+                        }
+                    } else {
+                        // The system apk may have been updated with an older
+                        // version of the one on the data partition, but which
+                        // granted a new system permission that it didn't have
+                        // before.  In this case we do want to allow the app to
+                        // now get the new permission if the ancestral apk is
+                        // privileged to get it.
+                        if (disabledPs != null && disabledPkg != null
+                                && isPackageRequestingPermission(disabledPkg, perm)
+                                && ((privilegedPermission && disabledPs.isPrivileged())
+                                        || (oemPermission && disabledPs.isOem()
+                                                && canGrantOemPermission(disabledPs, perm)))) {
+                            allowed = true;
+                        }
+                        // Also if a privileged parent package on the system image or any of
+                        // its children requested a privileged/oem permission, the updated child
+                        // packages can also get the permission.
+                        if (pkg.parentPackage != null) {
+                            final PackageParser.Package disabledParentPkg = mPackageManagerInt
+                                    .getDisabledPackage(pkg.parentPackage.packageName);
+                            final PackageSetting disabledParentPs = (disabledParentPkg != null)
+                                    ? (PackageSetting) disabledParentPkg.mExtras : null;
+                            if (disabledParentPkg != null
+                                    && ((privilegedPermission && disabledParentPs.isPrivileged())
+                                            || (oemPermission && disabledParentPs.isOem()))) {
+                                if (isPackageRequestingPermission(disabledParentPkg, perm)
+                                        && canGrantOemPermission(disabledParentPs, perm)) {
+                                    allowed = true;
+                                } else if (disabledParentPkg.childPackages != null) {
+                                    for (PackageParser.Package disabledChildPkg
+                                            : disabledParentPkg.childPackages) {
+                                        final PackageSetting disabledChildPs =
+                                                (disabledChildPkg != null)
+                                                        ? (PackageSetting) disabledChildPkg.mExtras
+                                                        : null;
+                                        if (isPackageRequestingPermission(disabledChildPkg, perm)
+                                                && canGrantOemPermission(
+                                                        disabledChildPs, perm)) {
+                                            allowed = true;
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                    allowed = (privilegedPermission && pkg.isPrivileged())
+                            || (oemPermission && pkg.isOem()
+                                    && canGrantOemPermission(ps, perm));
+                }
+            }
+        }
+        if (!allowed) {
+            if (!allowed
+                    && bp.isPre23()
+                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                // If this was a previously normal/dangerous permission that got moved
+                // to a system permission as part of the runtime permission redesign, then
+                // we still want to blindly grant it to old apps.
+                allowed = true;
+            }
+            if (!allowed && bp.isInstaller()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the system installer and
+                // this app is an installer, then it gets the permission.
+                allowed = true;
+            }
+            if (!allowed && bp.isVerifier()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the system verifier and
+                // this app is a verifier, then it gets the permission.
+                allowed = true;
+            }
+            if (!allowed && bp.isPreInstalled()
+                    && pkg.isSystem()) {
+                // Any pre-installed system app is allowed to get this permission.
+                allowed = true;
+            }
+            if (!allowed && bp.isDevelopment()) {
+                // For development permissions, a development permission
+                // is granted only if it was already granted.
+                allowed = origPermissions.hasInstallPermission(perm);
+            }
+            if (!allowed && bp.isSetup()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the system setup wizard and
+                // this app is a setup wizard, then it gets the permission.
+                allowed = true;
+            }
+        }
+        return allowed;
+    }
+
+    private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
+        if (!ps.isOem()) {
+            return false;
+        }
+        // all oem permissions must explicitly be granted or denied
+        final Boolean granted =
+                SystemConfig.getInstance().getOemPermissions(ps.name).get(permission);
+        if (granted == null) {
+            throw new IllegalStateException("OEM permission" + permission + " requested by package "
+                    + ps.name + " must be explicitly declared granted or not");
+        }
+        return Boolean.TRUE == granted;
+    }
+
+    private boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId) {
+        if (!mSettings.mPermissionReviewRequired) {
+            return false;
+        }
+
+        // Permission review applies only to apps not supporting the new permission model.
+        if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+            return false;
+        }
+
+        // Legacy apps have the permission and get user consent on launch.
+        if (pkg == null || pkg.mExtras == null) {
+            return false;
+        }
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        return permissionsState.isPermissionReviewRequired(userId);
+    }
+
+    private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) {
+        final int permCount = pkg.requestedPermissions.size();
+        for (int j = 0; j < permCount; j++) {
+            String requestedPermission = pkg.requestedPermissions.get(j);
+            if (permission.equals(requestedPermission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void grantRuntimePermissionsGrantedToDisabledPackageLocked(
             PackageParser.Package pkg, int callingUid, PermissionCallback callback) {
         if (pkg.parentPackage == null) {
@@ -744,7 +1493,8 @@
         }
     }
 
-    private int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, int[] allUserIds) {
+    private int[] revokeUnusedSharedUserPermissionsLocked(
+            SharedUserSetting suSetting, int[] allUserIds) {
         // Collect all used permissions in the UID
         final ArraySet<String> usedPermissions = new ArraySet<>();
         final List<PackageParser.Package> pkgList = suSetting.getPackages();
@@ -845,7 +1595,79 @@
         return permissionsState.getPermissionFlags(permName, userId);
     }
 
-    private int updatePermissions(String packageName, PackageParser.Package pkgInfo, int flags) {
+    private static final int UPDATE_PERMISSIONS_ALL = 1<<0;
+    private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
+    private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
+
+    private void updatePermissions(String packageName, PackageParser.Package pkg,
+            boolean replaceGrant, Collection<PackageParser.Package> allPackages,
+            PermissionCallback callback) {
+        final int flags = (pkg != null ? UPDATE_PERMISSIONS_ALL : 0) |
+                (replaceGrant ? UPDATE_PERMISSIONS_REPLACE_PKG : 0);
+        updatePermissions(
+                packageName, pkg, getVolumeUuidForPackage(pkg), flags, allPackages, callback);
+        if (pkg != null && pkg.childPackages != null) {
+            for (PackageParser.Package childPkg : pkg.childPackages) {
+                updatePermissions(childPkg.packageName, childPkg,
+                        getVolumeUuidForPackage(childPkg), flags, allPackages, callback);
+            }
+        }
+    }
+
+    private void updateAllPermissions(String volumeUuid, boolean sdkUpdated,
+            Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+        final int flags = UPDATE_PERMISSIONS_ALL |
+                (sdkUpdated
+                        ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL
+                        : 0);
+        updatePermissions(null, null, volumeUuid, flags, allPackages, callback);
+    }
+
+    private void updatePermissions(String changingPkgName, PackageParser.Package changingPkg,
+            String replaceVolumeUuid, int flags, Collection<PackageParser.Package> allPackages,
+            PermissionCallback callback) {
+        // TODO: Most of the methods exposing BasePermission internals [source package name,
+        // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
+        // have package settings, we should make note of it elsewhere [map between
+        // source package name and BasePermission] and cycle through that here. Then we
+        // define a single method on BasePermission that takes a PackageSetting, changing
+        // package name and a package.
+        // NOTE: With this approach, we also don't need to tree trees differently than
+        // normal permissions. Today, we need two separate loops because these BasePermission
+        // objects are stored separately.
+        // Make sure there are no dangling permission trees.
+        flags = updatePermissionTrees(changingPkgName, changingPkg, flags);
+
+        // Make sure all dynamic permissions have been assigned to a package,
+        // and make sure there are no dangling permissions.
+        flags = updatePermissions(changingPkgName, changingPkg, flags);
+
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
+        // Now update the permissions for all packages, in particular
+        // replace the granted permissions of the system packages.
+        if ((flags & UPDATE_PERMISSIONS_ALL) != 0) {
+            for (PackageParser.Package pkg : allPackages) {
+                if (pkg != changingPkg) {
+                    // Only replace for packages on requested volume
+                    final String volumeUuid = getVolumeUuidForPackage(pkg);
+                    final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
+                            && Objects.equals(replaceVolumeUuid, volumeUuid);
+                    grantPermissions(pkg, replace, changingPkgName, callback);
+                }
+            }
+        }
+
+        if (changingPkg != null) {
+            // Only replace for packages on requested volume
+            final String volumeUuid = getVolumeUuidForPackage(changingPkg);
+            final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
+                    && Objects.equals(replaceVolumeUuid, volumeUuid);
+            grantPermissions(changingPkg, replace, changingPkgName, callback);
+        }
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+    }
+
+    private int updatePermissions(String packageName, PackageParser.Package pkg, int flags) {
         Set<BasePermission> needsUpdate = null;
         synchronized (mLock) {
             final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator();
@@ -856,10 +1678,10 @@
                 }
                 if (bp.getSourcePackageSetting() != null) {
                     if (packageName != null && packageName.equals(bp.getSourcePackageName())
-                        && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+                        && (pkg == null || !hasPermission(pkg, bp.getName()))) {
                         Slog.i(TAG, "Removing old permission tree: " + bp.getName()
                                 + " from package " + bp.getSourcePackageName());
-                        flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+                        flags |= UPDATE_PERMISSIONS_ALL;
                         it.remove();
                     }
                     continue;
@@ -872,13 +1694,13 @@
         }
         if (needsUpdate != null) {
             for (final BasePermission bp : needsUpdate) {
-                final PackageParser.Package pkg =
+                final PackageParser.Package sourcePkg =
                         mPackageManagerInt.getPackage(bp.getSourcePackageName());
                 synchronized (mLock) {
-                    if (pkg != null && pkg.mExtras != null) {
-                        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                    if (sourcePkg != null && sourcePkg.mExtras != null) {
+                        final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras;
                         if (bp.getSourcePackageSetting() == null) {
-                            bp.setSourcePackageSetting(ps);
+                            bp.setSourcePackageSetting(sourcePs);
                         }
                         continue;
                     }
@@ -891,7 +1713,7 @@
         return flags;
     }
 
-    private int updatePermissionTrees(String packageName, PackageParser.Package pkgInfo,
+    private int updatePermissionTrees(String packageName, PackageParser.Package pkg,
             int flags) {
         Set<BasePermission> needsUpdate = null;
         synchronized (mLock) {
@@ -900,10 +1722,10 @@
                 final BasePermission bp = it.next();
                 if (bp.getSourcePackageSetting() != null) {
                     if (packageName != null && packageName.equals(bp.getSourcePackageName())
-                        && (pkgInfo == null || !hasPermission(pkgInfo, bp.getName()))) {
+                        && (pkg == null || !hasPermission(pkg, bp.getName()))) {
                         Slog.i(TAG, "Removing old permission tree: " + bp.getName()
                                 + " from package " + bp.getSourcePackageName());
-                        flags |= PackageManagerService.UPDATE_PERMISSIONS_ALL;
+                        flags |= UPDATE_PERMISSIONS_ALL;
                         it.remove();
                     }
                     continue;
@@ -916,13 +1738,13 @@
         }
         if (needsUpdate != null) {
             for (final BasePermission bp : needsUpdate) {
-                final PackageParser.Package pkg =
+                final PackageParser.Package sourcePkg =
                         mPackageManagerInt.getPackage(bp.getSourcePackageName());
                 synchronized (mLock) {
-                    if (pkg != null && pkg.mExtras != null) {
-                        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                    if (sourcePkg != null && sourcePkg.mExtras != null) {
+                        final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras;
                         if (bp.getSourcePackageSetting() == null) {
-                            bp.setSourcePackageSetting(ps);
+                            bp.setSourcePackageSetting(sourcePs);
                         }
                         continue;
                     }
@@ -985,7 +1807,7 @@
                 callback.onInstallPermissionUpdated();
             } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
                     || hadState) {
-                callback.onPermissionUpdated(userId);
+                callback.onPermissionUpdated(new int[] { userId }, false);
             }
         }
     }
@@ -1083,6 +1905,29 @@
         }
     }
 
+    private void systemReady() {
+        mSystemReady = true;
+        if (mPrivappPermissionsViolations != null) {
+            throw new IllegalStateException("Signature|privileged permissions not in "
+                    + "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
+        }
+    }
+
+    private static String getVolumeUuidForPackage(PackageParser.Package pkg) {
+        if (pkg == null) {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+        if (pkg.isExternal()) {
+            if (TextUtils.isEmpty(pkg.volumeUuid)) {
+                return StorageManager.UUID_PRIMARY_PHYSICAL;
+            } else {
+                return pkg.volumeUuid;
+            }
+        } else {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+    }
+
     private static boolean hasPermission(PackageParser.Package pkgInfo, String permName) {
         for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
             if (pkgInfo.permissions.get(i).info.name.equals(permName)) {
@@ -1154,10 +1999,22 @@
 
     private class PermissionManagerInternalImpl extends PermissionManagerInternal {
         @Override
+        public void systemReady() {
+            PermissionManagerService.this.systemReady();
+        }
+        @Override
+        public boolean isPermissionsReviewRequired(Package pkg, int userId) {
+            return PermissionManagerService.this.isPermissionsReviewRequired(pkg, userId);
+        }
+        @Override
         public void addAllPermissions(Package pkg, boolean chatty) {
             PermissionManagerService.this.addAllPermissions(pkg, chatty);
         }
         @Override
+        public void addAllPermissionGroups(Package pkg, boolean chatty) {
+            PermissionManagerService.this.addAllPermissionGroups(pkg, chatty);
+        }
+        @Override
         public void removeAllPermissions(Package pkg, boolean chatty) {
             PermissionManagerService.this.removeAllPermissions(pkg, chatty);
         }
@@ -1198,10 +2055,16 @@
                     overridePolicy, callingUid, userId, callback);
         }
         @Override
-        public int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting,
-                int[] allUserIds) {
-            return PermissionManagerService.this.revokeUnusedSharedUserPermissions(
-                    (SharedUserSetting) suSetting, allUserIds);
+        public void updatePermissions(String packageName, Package pkg, boolean replaceGrant,
+                Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+            PermissionManagerService.this.updatePermissions(
+                    packageName, pkg, replaceGrant, allPackages, callback);
+        }
+        @Override
+        public void updateAllPermissions(String volumeUuid, boolean sdkUpdated,
+                Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+            PermissionManagerService.this.updateAllPermissions(
+                    volumeUuid, sdkUpdated, allPackages, callback);
         }
         @Override
         public String[] getAppOpPermissionPackages(String permName) {
@@ -1214,16 +2077,6 @@
                     callingUid, userId);
         }
         @Override
-        public int updatePermissions(String packageName,
-                PackageParser.Package pkgInfo, int flags) {
-            return PermissionManagerService.this.updatePermissions(packageName, pkgInfo, flags);
-        }
-        @Override
-        public int updatePermissionTrees(String packageName,
-                PackageParser.Package pkgInfo, int flags) {
-            return PermissionManagerService.this.updatePermissionTrees(packageName, pkgInfo, flags);
-        }
-        @Override
         public void updatePermissionFlags(String permName, String packageName, int flagMask,
                 int flagValues, int callingUid, int userId, PermissionCallback callback) {
             PermissionManagerService.this.updatePermissionFlags(
@@ -1252,6 +2105,20 @@
                     permName, packageName, callingUid, userId);
         }
         @Override
+        public int checkUidPermission(String permName, int uid, int callingUid) {
+            return PermissionManagerService.this.checkUidPermission(permName, uid, callingUid);
+        }
+        @Override
+        public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags,
+                int callingUid) {
+            return PermissionManagerService.this.getPermissionGroupInfo(
+                    groupName, flags, callingUid);
+        }
+        @Override
+        public List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) {
+            return PermissionManagerService.this.getAllPermissionGroups(flags, callingUid);
+        }
+        @Override
         public PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
                 int callingUid) {
             return PermissionManagerService.this.getPermissionInfo(
@@ -1276,17 +2143,5 @@
                 return mSettings.getPermissionLocked(permName);
             }
         }
-        @Override
-        public void putPermissionTEMP(String permName, BasePermission permission) {
-            synchronized (PermissionManagerService.this.mLock) {
-                mSettings.putPermissionLocked(permName, (BasePermission) permission);
-            }
-        }
-        @Override
-        public Iterator<BasePermission> getPermissionIteratorTEMP() {
-            synchronized (PermissionManagerService.this.mLock) {
-                return mSettings.getAllPermissionsLocked().iterator();
-            }
-        }
     }
 }
diff --git a/com/android/server/pm/permission/PermissionSettings.java b/com/android/server/pm/permission/PermissionSettings.java
index 7d125c9..f6c4990 100644
--- a/com/android/server/pm/permission/PermissionSettings.java
+++ b/com/android/server/pm/permission/PermissionSettings.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.PackageParser;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -46,7 +47,8 @@
  */
 public class PermissionSettings {
 
-    final boolean mPermissionReviewRequired;
+    public final boolean mPermissionReviewRequired;
+
     /**
      * All of the permissions known to the system. The mapping is from permission
      * name to permission object.
@@ -64,6 +66,14 @@
             new ArrayMap<String, BasePermission>();
 
     /**
+     * All permisson groups know to the system. The mapping is from permission group
+     * name to permission group object.
+     */
+    @GuardedBy("mLock")
+    final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups =
+            new ArrayMap<String, PackageParser.PermissionGroup>();
+
+    /**
      * Set of packages that request a particular app op. The mapping is from permission
      * name to package names.
      */
diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java
index b917dae..2494bde 100644
--- a/com/android/server/power/PowerManagerService.java
+++ b/com/android/server/power/PowerManagerService.java
@@ -58,10 +58,6 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.dreams.DreamManagerInternal;
-import android.service.power.PowerServiceDumpProto;
-import android.service.power.PowerServiceSettingsAndConfigurationDumpProto;
-import android.service.power.SuspendBlockerProto;
-import android.service.power.WakeLockProto;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
 import android.util.EventLog;
@@ -620,8 +616,8 @@
         }
 
         void dumpProto(ProtoOutputStream proto) {
-            final long constantsToken = proto.start(PowerServiceDumpProto.CONSTANTS);
-            proto.write(PowerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS,
+            final long constantsToken = proto.start(PowerManagerServiceDumpProto.CONSTANTS);
+            proto.write(PowerManagerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS,
                     NO_CACHED_WAKE_LOCKS);
             proto.end(constantsToken);
         }
@@ -3396,112 +3392,112 @@
 
         synchronized (mLock) {
             mConstants.dumpProto(proto);
-            proto.write(PowerServiceDumpProto.DIRTY, mDirty);
-            proto.write(PowerServiceDumpProto.WAKEFULNESS, mWakefulness);
-            proto.write(PowerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging);
-            proto.write(PowerServiceDumpProto.IS_POWERED, mIsPowered);
-            proto.write(PowerServiceDumpProto.PLUG_TYPE, mPlugType);
-            proto.write(PowerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
+            proto.write(PowerManagerServiceDumpProto.DIRTY, mDirty);
+            proto.write(PowerManagerServiceDumpProto.WAKEFULNESS, mWakefulness);
+            proto.write(PowerManagerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging);
+            proto.write(PowerManagerServiceDumpProto.IS_POWERED, mIsPowered);
+            proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType);
+            proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
             proto.write(
-                    PowerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
+                    PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
                     mBatteryLevelWhenDreamStarted);
-            proto.write(PowerServiceDumpProto.DOCK_STATE, mDockState);
-            proto.write(PowerServiceDumpProto.IS_STAY_ON, mStayOn);
-            proto.write(PowerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
-            proto.write(PowerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted);
-            proto.write(PowerServiceDumpProto.IS_SYSTEM_READY, mSystemReady);
+            proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState);
+            proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn);
+            proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
+            proto.write(PowerManagerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted);
+            proto.write(PowerManagerServiceDumpProto.IS_SYSTEM_READY, mSystemReady);
             proto.write(
-                    PowerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED,
+                    PowerManagerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED,
                     mHalAutoSuspendModeEnabled);
             proto.write(
-                    PowerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED,
+                    PowerManagerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED,
                     mHalInteractiveModeEnabled);
 
-            final long activeWakeLocksToken = proto.start(PowerServiceDumpProto.ACTIVE_WAKE_LOCKS);
+            final long activeWakeLocksToken = proto.start(PowerManagerServiceDumpProto.ACTIVE_WAKE_LOCKS);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_CPU,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_CPU,
                     (mWakeLockSummary & WAKE_LOCK_CPU) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT,
                     (mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM,
                     (mWakeLockSummary & WAKE_LOCK_SCREEN_DIM) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT,
                     (mWakeLockSummary & WAKE_LOCK_BUTTON_BRIGHT) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF,
                     (mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE,
                     (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE,
                     (mWakeLockSummary & WAKE_LOCK_DOZE) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW,
                     (mWakeLockSummary & WAKE_LOCK_DRAW) != 0);
             proto.end(activeWakeLocksToken);
 
-            proto.write(PowerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled);
-            proto.write(PowerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched);
-            proto.write(PowerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck);
+            proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled);
+            proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched);
+            proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck);
 
-            final long userActivityToken = proto.start(PowerServiceDumpProto.USER_ACTIVITY);
+            final long userActivityToken = proto.start(PowerManagerServiceDumpProto.USER_ACTIVITY);
             proto.write(
-                    PowerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT,
+                    PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT,
                     (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0);
             proto.write(
-                    PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM,
+                    PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM,
                     (mUserActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0);
             proto.write(
-                    PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM,
+                    PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM,
                     (mUserActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0);
             proto.end(userActivityToken);
 
             proto.write(
-                    PowerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY,
+                    PowerManagerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY,
                     mRequestWaitForNegativeProximity);
-            proto.write(PowerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled);
-            proto.write(PowerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned);
-            proto.write(PowerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled);
-            proto.write(PowerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow);
-            proto.write(PowerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode);
-            proto.write(PowerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode);
+            proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled);
+            proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned);
+            proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled);
+            proto.write(PowerManagerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow);
+            proto.write(PowerManagerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode);
+            proto.write(PowerManagerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode);
 
             for (int id : mDeviceIdleWhitelist) {
-                proto.write(PowerServiceDumpProto.DEVICE_IDLE_WHITELIST, id);
+                proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_WHITELIST, id);
             }
             for (int id : mDeviceIdleTempWhitelist) {
-                proto.write(PowerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
+                proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
             }
 
-            proto.write(PowerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime);
-            proto.write(PowerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime);
-            proto.write(PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime);
+            proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime);
+            proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime);
+            proto.write(PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime);
             proto.write(
-                    PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS,
+                    PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS,
                     mLastUserActivityTimeNoChangeLights);
             proto.write(
-                    PowerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS,
+                    PowerManagerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS,
                     mLastInteractivePowerHintTime);
             proto.write(
-                    PowerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS,
+                    PowerManagerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS,
                     mLastScreenBrightnessBoostTime);
             proto.write(
-                    PowerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS,
+                    PowerManagerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS,
                     mScreenBrightnessBoostInProgress);
-            proto.write(PowerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady);
+            proto.write(PowerManagerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady);
             proto.write(
-                    PowerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER,
+                    PowerManagerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER,
                     mHoldingWakeLockSuspendBlocker);
             proto.write(
-                    PowerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER,
+                    PowerManagerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER,
                     mHoldingDisplaySuspendBlocker);
 
             final long settingsAndConfigurationToken =
-                    proto.start(PowerServiceDumpProto.SETTINGS_AND_CONFIGURATION);
+                    proto.start(PowerManagerServiceDumpProto.SETTINGS_AND_CONFIGURATION);
             proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto
                             .IS_DECOUPLE_HAL_AUTO_SUSPEND_MODE_FROM_DISPLAY_CONFIG,
@@ -3698,42 +3694,43 @@
             final int sleepTimeout = getSleepTimeoutLocked();
             final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
             final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
-            proto.write(PowerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout);
-            proto.write(PowerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout);
-            proto.write(PowerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration);
-            proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging);
-            proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged);
+            proto.write(PowerManagerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout);
+            proto.write(PowerManagerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout);
+            proto.write(PowerManagerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration);
+            proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging);
+            proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged);
 
             for (int i = 0; i < mUidState.size(); i++) {
                 final UidState state = mUidState.valueAt(i);
-                final long uIDToken = proto.start(PowerServiceDumpProto.UIDS);
+                final long uIDToken = proto.start(PowerManagerServiceDumpProto.UID_STATES);
                 final int uid = mUidState.keyAt(i);
-                proto.write(PowerServiceDumpProto.UidProto.UID, uid);
-                proto.write(PowerServiceDumpProto.UidProto.UID_STRING, UserHandle.formatUid(uid));
-                proto.write(PowerServiceDumpProto.UidProto.IS_ACTIVE, state.mActive);
-                proto.write(PowerServiceDumpProto.UidProto.NUM_WAKE_LOCKS, state.mNumWakeLocks);
+                proto.write(PowerManagerServiceDumpProto.UidStateProto.UID, uid);
+                proto.write(PowerManagerServiceDumpProto.UidStateProto.UID_STRING, UserHandle.formatUid(uid));
+                proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_ACTIVE, state.mActive);
+                proto.write(PowerManagerServiceDumpProto.UidStateProto.NUM_WAKE_LOCKS, state.mNumWakeLocks);
                 if (state.mProcState == ActivityManager.PROCESS_STATE_UNKNOWN) {
-                    proto.write(PowerServiceDumpProto.UidProto.IS_PROCESS_STATE_UNKNOWN, true);
+                    proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_PROCESS_STATE_UNKNOWN, true);
                 } else {
-                    proto.write(PowerServiceDumpProto.UidProto.PROCESS_STATE, state.mProcState);
+                    proto.write(PowerManagerServiceDumpProto.UidStateProto.PROCESS_STATE,
+                            ActivityManager.processStateAmToProto(state.mProcState));
                 }
                 proto.end(uIDToken);
             }
 
-            mHandler.getLooper().writeToProto(proto, PowerServiceDumpProto.LOOPER);
+            mHandler.getLooper().writeToProto(proto, PowerManagerServiceDumpProto.LOOPER);
 
             for (WakeLock wl : mWakeLocks) {
-                wl.writeToProto(proto, PowerServiceDumpProto.WAKE_LOCKS);
+                wl.writeToProto(proto, PowerManagerServiceDumpProto.WAKE_LOCKS);
             }
 
             for (SuspendBlocker sb : mSuspendBlockers) {
-                sb.writeToProto(proto, PowerServiceDumpProto.SUSPEND_BLOCKERS);
+                sb.writeToProto(proto, PowerManagerServiceDumpProto.SUSPEND_BLOCKERS);
             }
             wcd = mWirelessChargerDetector;
         }
 
         if (wcd != null) {
-            wcd.writeToProto(proto, PowerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
+            wcd.writeToProto(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
         }
         proto.flush();
     }
diff --git a/com/android/server/power/WirelessChargerDetector.java b/com/android/server/power/WirelessChargerDetector.java
index 6ee9dcd..54487e3 100644
--- a/com/android/server/power/WirelessChargerDetector.java
+++ b/com/android/server/power/WirelessChargerDetector.java
@@ -24,7 +24,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
-import android.service.power.WirelessChargerDetectorProto;
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
diff --git a/com/android/server/usage/AppIdleHistory.java b/com/android/server/usage/AppIdleHistory.java
index f298559..e5d3915 100644
--- a/com/android/server/usage/AppIdleHistory.java
+++ b/com/android/server/usage/AppIdleHistory.java
@@ -16,6 +16,9 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.AppStandby.*;
+
+import android.app.usage.AppStandby;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.util.ArrayMap;
@@ -51,8 +54,10 @@
 
     private static final String TAG = "AppIdleHistory";
 
+    private static final boolean DEBUG = AppStandbyController.DEBUG;
+
     // History for all users and all packages
-    private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>();
+    private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
     private long mLastPeriod = 0;
     private static final long ONE_MINUTE = 60 * 1000;
     private static final int HISTORY_SIZE = 100;
@@ -70,6 +75,13 @@
     private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
     // Elapsed timebase time when app was last used
     private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
+    private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
+    private static final String ATTR_BUCKETING_REASON = "bucketReason";
+
+    // State that was last informed to listeners, since boot
+    private static final int STATE_UNINFORMED = 0;
+    private static final int STATE_ACTIVE = 1;
+    private static final int STATE_IDLE = 2;
 
     // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
     private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
@@ -85,17 +97,15 @@
 
     private boolean mScreenOn;
 
-    private static class PackageHistory {
+    private static class AppUsageHistory {
         final byte[] recent = new byte[HISTORY_SIZE];
         long lastUsedElapsedTime;
         long lastUsedScreenTime;
+        @StandbyBuckets int currentBucket;
+        String bucketingReason;
+        int lastInformedState;
     }
 
-    AppIdleHistory(long elapsedRealtime) {
-        this(Environment.getDataSystemDirectory(), elapsedRealtime);
-    }
-
-    @VisibleForTesting
     AppIdleHistory(File storageDir, long elapsedRealtime) {
         mElapsedSnapshot = elapsedRealtime;
         mScreenOnSnapshot = elapsedRealtime;
@@ -119,6 +129,9 @@
             mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
             mElapsedSnapshot = elapsedRealtime;
         }
+        if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot
+                + ", mScreenOnDuration=" + mScreenOnDuration
+                + ", mScreenOn=" + mScreenOn);
     }
 
     public long getScreenOnTime(long elapsedRealtime) {
@@ -174,29 +187,35 @@
     }
 
     public void reportUsage(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime);
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
 
         shiftHistoryToNow(userHistory, elapsedRealtime);
 
-        packageHistory.lastUsedElapsedTime = mElapsedDuration
+        appUsageHistory.lastUsedElapsedTime = mElapsedDuration
                 + (elapsedRealtime - mElapsedSnapshot);
-        packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
-        packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
+        appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
+        appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
+        appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE;
+        appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+        if (DEBUG) {
+            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                    + ", reason=" + appUsageHistory.bucketingReason);
+        }
     }
 
     public void setIdle(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime);
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
 
         shiftHistoryToNow(userHistory, elapsedRealtime);
 
-        packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
+        appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
     }
 
-    private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory,
+    private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory,
             long elapsedRealtime) {
         long thisPeriod = elapsedRealtime / PERIOD_DURATION;
         // Has the period switched over? Slide all users' package histories
@@ -206,7 +225,7 @@
             final int NUSERS = mIdleHistory.size();
             for (int u = 0; u < NUSERS; u++) {
                 userHistory = mIdleHistory.valueAt(u);
-                for (PackageHistory idleState : userHistory.values()) {
+                for (AppUsageHistory idleState : userHistory.values()) {
                     // Shift left
                     System.arraycopy(idleState.recent, diff, idleState.recent, 0,
                             HISTORY_SIZE - diff);
@@ -221,8 +240,8 @@
         mLastPeriod = thisPeriod;
     }
 
-    private ArrayMap<String, PackageHistory> getUserHistory(int userId) {
-        ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+    private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
+        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         if (userHistory == null) {
             userHistory = new ArrayMap<>();
             mIdleHistory.put(userId, userHistory);
@@ -231,16 +250,18 @@
         return userHistory;
     }
 
-    private PackageHistory getPackageHistory(ArrayMap<String, PackageHistory> userHistory,
-            String packageName, long elapsedRealtime) {
-        PackageHistory packageHistory = userHistory.get(packageName);
-        if (packageHistory == null) {
-            packageHistory = new PackageHistory();
-            packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
-            packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
-            userHistory.put(packageName, packageHistory);
+    private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory,
+            String packageName, long elapsedRealtime, boolean create) {
+        AppUsageHistory appUsageHistory = userHistory.get(packageName);
+        if (appUsageHistory == null && create) {
+            appUsageHistory = new AppUsageHistory();
+            appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
+            appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
+            appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_NEVER;
+            appUsageHistory.bucketingReason = REASON_DEFAULT;
+            userHistory.put(packageName, appUsageHistory);
         }
-        return packageHistory;
+        return appUsageHistory;
     }
 
     public void onUserRemoved(int userId) {
@@ -248,48 +269,124 @@
     }
 
     public boolean isIdle(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory =
-                getPackageHistory(userHistory, packageName, elapsedRealtime);
-        if (packageHistory == null) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        if (appUsageHistory == null) {
             return false; // Default to not idle
         } else {
-            return hasPassedThresholds(packageHistory, elapsedRealtime);
+            return appUsageHistory.currentBucket >= AppStandby.STANDBY_BUCKET_RARE;
+            // Whether or not it's passed will now be externally calculated and the
+            // bucket will be pushed to the history using setAppStandbyBucket()
+            //return hasPassedThresholds(appUsageHistory, elapsedRealtime);
         }
     }
 
+    public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
+            int bucket, String reason) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        appUsageHistory.currentBucket = bucket;
+        appUsageHistory.bucketingReason = reason;
+        if (DEBUG) {
+            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                    + ", reason=" + appUsageHistory.bucketingReason);
+        }
+    }
+
+    public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        return appUsageHistory.currentBucket;
+    }
+
+    public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, false);
+        return appUsageHistory != null ? appUsageHistory.bucketingReason : null;
+    }
+
     private long getElapsedTime(long elapsedRealtime) {
         return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
     }
 
     public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime);
-        packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime)
-                - mElapsedTimeThreshold;
-        packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime)
-                - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */;
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
+        if (idle) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
+            appUsageHistory.bucketingReason = REASON_FORCED;
+        } else {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+            // This is to pretend that the app was just used, don't freeze the state anymore.
+            appUsageHistory.bucketingReason = REASON_USAGE;
+        }
     }
 
     public void clearUsage(String packageName, int userId) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         userHistory.remove(packageName);
     }
 
-    private boolean hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime) {
-        return (packageHistory.lastUsedScreenTime
-                    <= getScreenOnTime(elapsedRealtime) - mScreenOnTimeThreshold)
-                && (packageHistory.lastUsedElapsedTime
-                        <= getElapsedTime(elapsedRealtime) - mElapsedTimeThreshold);
+    boolean shouldInformListeners(String packageName, int userId,
+            long elapsedRealtime, boolean isIdle) {
+        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;
+            return true;
+        }
+        return false;
     }
 
-    private File getUserFile(int userId) {
+    /**
+     * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds
+     * that corresponds to how long since the app was used.
+     * @param packageName
+     * @param userId
+     * @param elapsedRealtime current time
+     * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0
+     * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0
+     * @return The index whose values the app's used time exceeds (in both arrays)
+     */
+    int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
+            long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, false);
+        // If we don't have any state for the app, assume never used
+        if (appUsageHistory == null) return screenTimeThresholds.length - 1;
+
+        long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
+        long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
+
+        if (DEBUG) Slog.d(TAG, packageName
+                + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime
+                + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime);
+        if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta
+                + ", elapsed=" + elapsedDelta);
+        for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
+            if (screenOnDelta >= screenTimeThresholds[i]
+                && elapsedDelta >= elapsedTimeThresholds[i]) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    @VisibleForTesting
+    File getUserFile(int userId) {
         return new File(new File(new File(mStorageDir, "users"),
                 Integer.toString(userId)), APP_IDLE_FILENAME);
     }
 
-    private void readAppIdleTimes(int userId, ArrayMap<String, PackageHistory> userHistory) {
+    private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) {
         FileInputStream fis = null;
         try {
             AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
@@ -315,12 +412,22 @@
                     final String name = parser.getName();
                     if (name.equals(TAG_PACKAGE)) {
                         final String packageName = parser.getAttributeValue(null, ATTR_NAME);
-                        PackageHistory packageHistory = new PackageHistory();
-                        packageHistory.lastUsedElapsedTime =
+                        AppUsageHistory appUsageHistory = new AppUsageHistory();
+                        appUsageHistory.lastUsedElapsedTime =
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
-                        packageHistory.lastUsedScreenTime =
+                        appUsageHistory.lastUsedScreenTime =
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
-                        userHistory.put(packageName, packageHistory);
+                        String currentBucketString = parser.getAttributeValue(null,
+                                ATTR_CURRENT_BUCKET);
+                        appUsageHistory.currentBucket = currentBucketString == null
+                                ? AppStandby.STANDBY_BUCKET_ACTIVE
+                                : Integer.parseInt(currentBucketString);
+                        appUsageHistory.bucketingReason =
+                                parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
+                        if (appUsageHistory.bucketingReason == null) {
+                            appUsageHistory.bucketingReason = REASON_DEFAULT;
+                        }
+                        userHistory.put(packageName, appUsageHistory);
                     }
                 }
             }
@@ -345,17 +452,20 @@
 
             xml.startTag(null, TAG_PACKAGES);
 
-            ArrayMap<String,PackageHistory> userHistory = getUserHistory(userId);
+            ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId);
             final int N = userHistory.size();
             for (int i = 0; i < N; i++) {
                 String packageName = userHistory.keyAt(i);
-                PackageHistory history = userHistory.valueAt(i);
+                AppUsageHistory history = userHistory.valueAt(i);
                 xml.startTag(null, TAG_PACKAGE);
                 xml.attribute(null, ATTR_NAME, packageName);
                 xml.attribute(null, ATTR_ELAPSED_IDLE,
                         Long.toString(history.lastUsedElapsedTime));
                 xml.attribute(null, ATTR_SCREEN_IDLE,
                         Long.toString(history.lastUsedScreenTime));
+                xml.attribute(null, ATTR_CURRENT_BUCKET,
+                        Integer.toString(history.currentBucket));
+                xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
                 xml.endTag(null, TAG_PACKAGE);
             }
 
@@ -368,10 +478,10 @@
         }
     }
 
-    public void dump(IndentingPrintWriter idpw, int userId) {
+    public void dump(IndentingPrintWriter idpw, int userId, String pkg) {
         idpw.println("Package idle stats:");
         idpw.increaseIndent();
-        ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long totalElapsedTime = getElapsedTime(elapsedRealtime);
         final long screenOnTime = getScreenOnTime(elapsedRealtime);
@@ -379,13 +489,18 @@
         final int P = userHistory.size();
         for (int p = 0; p < P; p++) {
             final String packageName = userHistory.keyAt(p);
-            final PackageHistory packageHistory = userHistory.valueAt(p);
+            final AppUsageHistory appUsageHistory = userHistory.valueAt(p);
+            if (pkg != null && !pkg.equals(packageName)) {
+                continue;
+            }
             idpw.print("package=" + packageName);
             idpw.print(" lastUsedElapsed=");
-            TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw);
+            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw);
             idpw.print(" lastUsedScreenOn=");
-            TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw);
+            TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
             idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
+            idpw.print(" bucket=" + appUsageHistory.currentBucket
+                    + " reason=" + appUsageHistory.bucketingReason);
             idpw.println();
         }
         idpw.println();
@@ -399,7 +514,7 @@
     }
 
     public void dumpHistory(IndentingPrintWriter idpw, int userId) {
-        ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         if (userHistory == null) return;
         final int P = userHistory.size();
diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java
index b2446ba..17fde57 100644
--- a/com/android/server/usage/AppStandbyController.java
+++ b/com/android/server/usage/AppStandbyController.java
@@ -18,13 +18,14 @@
 
 import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
-import static com.android.server.usage.UsageStatsService.MSG_REPORT_EVENT;
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.admin.DevicePolicyManager;
+import android.app.usage.AppStandby;
+import android.app.usage.AppStandby.StandbyBuckets;
 import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
 import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -41,6 +42,7 @@
 import android.net.NetworkScoreManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.IDeviceIdleController;
 import android.os.Looper;
@@ -66,8 +68,10 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 
+import java.io.File;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -76,10 +80,33 @@
 public class AppStandbyController {
 
     private static final String TAG = "AppStandbyController";
-    private static final boolean DEBUG = false;
+    static final boolean DEBUG = false;
 
     static final boolean COMPRESS_TIME = false;
     private static final long ONE_MINUTE = 60 * 1000;
+    private static final long ONE_HOUR = ONE_MINUTE * 60;
+    private static final long ONE_DAY = ONE_HOUR * 24;
+
+    static final long[] SCREEN_TIME_THRESHOLDS = {
+            0,
+            0,
+            COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
+            COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR
+    };
+
+    static final long[] ELAPSED_TIME_THRESHOLDS = {
+            0,
+            COMPRESS_TIME ?  1 * ONE_MINUTE : 12 * ONE_HOUR,
+            COMPRESS_TIME ?  4 * ONE_MINUTE :  2 * ONE_DAY,
+            COMPRESS_TIME ? 16 * ONE_MINUTE :  8 * ONE_DAY
+    };
+
+    static final int[] THRESHOLD_BUCKETS = {
+            AppStandby.STANDBY_BUCKET_ACTIVE,
+            AppStandby.STANDBY_BUCKET_WORKING_SET,
+            AppStandby.STANDBY_BUCKET_FREQUENT,
+            AppStandby.STANDBY_BUCKET_RARE
+    };
 
     // To name the lock for stack traces
     static class Lock {}
@@ -92,7 +119,7 @@
     private AppIdleHistory mAppIdleHistory;
 
     @GuardedBy("mAppIdleLock")
-    private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
+    private ArrayList<AppIdleStateChangeListener>
             mPackageAccessListeners = new ArrayList<>();
 
     /** Whether we've queried the list of carrier privileged apps. */
@@ -118,6 +145,9 @@
     long mAppIdleWallclockThresholdMillis;
     long mAppIdleParoleIntervalMillis;
     long mAppIdleParoleDurationMillis;
+    long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
+    long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
+
     boolean mAppIdleEnabled;
     boolean mAppIdleTempParoled;
     boolean mCharging;
@@ -129,20 +159,26 @@
     private final Handler mHandler;
     private final Context mContext;
 
-    private DisplayManager mDisplayManager;
-    private IDeviceIdleController mDeviceIdleController;
+    // TODO: Provide a mechanism to set an external bucketing service
+    private boolean mUseInternalBucketingHeuristics = true;
+
     private AppWidgetManager mAppWidgetManager;
-    private IBatteryStats mBatteryStats;
     private PowerManager mPowerManager;
     private PackageManager mPackageManager;
-    private PackageManagerInternal mPackageManagerInternal;
+    private Injector mInjector;
+
 
     AppStandbyController(Context context, Looper looper) {
-        mContext = context;
-        mHandler = new AppStandbyHandler(looper);
+        this(new Injector(context, looper));
+    }
+
+    AppStandbyController(Injector injector) {
+        mInjector = injector;
+        mContext = mInjector.getContext();
+        mHandler = new AppStandbyHandler(mInjector.getLooper());
         mPackageManager = mContext.getPackageManager();
-        mAppIdleEnabled = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableAutoPowerModes);
+        mAppIdleEnabled = mInjector.isAppIdleEnabled();
+
         if (mAppIdleEnabled) {
             IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
             deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
@@ -150,7 +186,8 @@
             mContext.registerReceiver(new DeviceStateReceiver(), deviceStates);
         }
         synchronized (mAppIdleLock) {
-            mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
+            mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
+                    mInjector.elapsedRealtime());
         }
 
         IntentFilter packageFilter = new IntentFilter();
@@ -164,6 +201,7 @@
     }
 
     public void onBootPhase(int phase) {
+        mInjector.onBootPhase(phase);
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
             // Observe changes to the threshold
             SettingsObserver settingsObserver = new SettingsObserver(mHandler);
@@ -171,18 +209,11 @@
             settingsObserver.updateSettings();
 
             mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
-            mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
-                    ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-            mBatteryStats = IBatteryStats.Stub.asInterface(
-                    ServiceManager.getService(BatteryStats.SERVICE_NAME));
-            mDisplayManager = (DisplayManager) mContext.getSystemService(
-                    Context.DISPLAY_SERVICE);
             mPowerManager = mContext.getSystemService(PowerManager.class);
-            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
 
-            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+            mInjector.registerDisplayListener(mDisplayListener, mHandler);
             synchronized (mAppIdleLock) {
-                mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
+                mAppIdleHistory.updateDisplay(isDisplayOn(), mInjector.elapsedRealtime());
             }
 
             if (mPendingOneTimeCheckIdleStates) {
@@ -191,7 +222,7 @@
 
             mSystemServicesReady = true;
         } else if (phase == PHASE_BOOT_COMPLETED) {
-            setChargingState(mContext.getSystemService(BatteryManager.class).isCharging());
+            setChargingState(mInjector.isCharging());
         }
     }
 
@@ -229,7 +260,7 @@
     /** Paroled here means temporary pardon from being inactive */
     void setAppIdleParoled(boolean paroled) {
         synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
+            final long now = mInjector.currentTimeMillis();
             if (mAppIdleTempParoled != paroled) {
                 mAppIdleTempParoled = paroled;
                 if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
@@ -284,7 +315,7 @@
      * scheduling a series of repeating checkIdleStates each time we fired off one.
      */
     void postOneTimeCheckIdleStates() {
-        if (mDeviceIdleController == null) {
+        if (mInjector.getBootPhase() < PHASE_SYSTEM_SERVICES_READY) {
             // Not booted yet; wait for it!
             mPendingOneTimeCheckIdleStates = true;
         } else {
@@ -304,7 +335,7 @@
 
         final int[] runningUserIds;
         try {
-            runningUserIds = ActivityManager.getService().getRunningUserIds();
+            runningUserIds = mInjector.getRunningUserIds();
             if (checkUserId != UserHandle.USER_ALL
                     && !ArrayUtils.contains(runningUserIds, checkUserId)) {
                 return false;
@@ -313,7 +344,7 @@
             throw re.rethrowFromSystemServer();
         }
 
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
         for (int i = 0; i < runningUserIds.length; i++) {
             final int userId = runningUserIds[i];
             if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
@@ -329,30 +360,71 @@
             for (int p = 0; p < packageCount; p++) {
                 final PackageInfo pi = packages.get(p);
                 final String packageName = pi.packageName;
-                final boolean isIdle = isAppIdleFiltered(packageName,
+                final boolean isSpecial = isAppSpecial(packageName,
                         UserHandle.getAppId(pi.applicationInfo.uid),
-                        userId, elapsedRealtime);
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                        userId, isIdle ? 1 : 0, packageName));
-                if (isIdle) {
+                        userId);
+                if (DEBUG) {
+                    Slog.d(TAG, "   Checking idle state for " + packageName);
+                }
+                if (isSpecial) {
+                    maybeInformListeners(packageName, userId, elapsedRealtime, false);
+                } else if (mUseInternalBucketingHeuristics) {
                     synchronized (mAppIdleLock) {
-                        mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
+                        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))) {
+                            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 (DEBUG) {
             Slog.d(TAG, "checkIdleStates took "
-                    + (SystemClock.elapsedRealtime() - elapsedRealtime));
+                    + (mInjector.elapsedRealtime() - elapsedRealtime));
         }
         return true;
     }
 
+    private void maybeInformListeners(String packageName, int userId,
+            long elapsedRealtime, boolean isIdle) {
+        synchronized (mAppIdleLock) {
+            if (mAppIdleHistory.shouldInformListeners(packageName, userId,
+                    elapsedRealtime, isIdle)) {
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+                        userId, isIdle ? 1 : 0, packageName));
+            }
+        }
+    }
+
+    @StandbyBuckets int getBucketForLocked(String packageName, int userId,
+            long elapsedRealtime) {
+        int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId,
+                elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds);
+        return THRESHOLD_BUCKETS[bucketIndex];
+    }
+
     /** Check if it's been a while since last parole and let idle apps do some work */
     void checkParoleTimeout() {
         boolean setParoled = false;
         synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
+            final long now = mInjector.currentTimeMillis();
             if (!mAppIdleTempParoled) {
                 final long timeSinceLastParole = now - mLastAppIdleParoledTime;
                 if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
@@ -374,10 +446,10 @@
             final int uid = mPackageManager.getPackageUidAsUser(packageName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
             if (idle) {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
+                mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
                         packageName, uid);
             } else {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
+                mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
                         packageName, uid);
             }
         } catch (PackageManager.NameNotFoundException | RemoteException e) {
@@ -389,7 +461,7 @@
         if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
         boolean paroled = false;
         synchronized (mAppIdleLock) {
-            final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
+            final long timeSinceLastParole = mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
             if (!deviceIdle
                     && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
                 if (DEBUG) {
@@ -419,13 +491,11 @@
                     || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
                 mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
                 if (previouslyIdle) {
-                    mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
-                                /* idle = */ 0, event.mPackage));
+                    maybeInformListeners(event.mPackage, userId, elapsedRealtime, false);
                     notifyBatteryStats(event.mPackage, userId, false);
                 }
             }
         }
-
     }
 
     /**
@@ -439,7 +509,7 @@
     void forceIdleState(String packageName, int userId, boolean idle) {
         final int appId = getAppId(packageName);
         if (appId < 0) return;
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
 
         final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
                 userId, elapsedRealtime);
@@ -470,7 +540,7 @@
         }
     }
 
-    void addListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+    void addListener(AppIdleStateChangeListener listener) {
         synchronized (mAppIdleLock) {
             if (!mPackageAccessListeners.contains(listener)) {
                 mPackageAccessListeners.add(listener);
@@ -478,7 +548,7 @@
         }
     }
 
-    void removeListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+    void removeListener(AppIdleStateChangeListener listener) {
         synchronized (mAppIdleLock) {
             mPackageAccessListeners.remove(listener);
         }
@@ -501,12 +571,66 @@
             return false;
         }
         if (shouldObfuscateInstantApps &&
-                mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
+                mInjector.isPackageEphemeral(userId, packageName)) {
             return false;
         }
         return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
     }
 
+    /** Returns true if this app should be whitelisted for some reason, to never go into standby */
+    boolean isAppSpecial(String packageName, int appId, int userId) {
+        if (packageName == null) return false;
+        // If not enabled at all, of course nobody is ever idle.
+        if (!mAppIdleEnabled) {
+            return true;
+        }
+        if (appId < Process.FIRST_APPLICATION_UID) {
+            // System uids never go idle.
+            return true;
+        }
+        if (packageName.equals("android")) {
+            // Nor does the framework (which should be redundant with the above, but for MR1 we will
+            // retain this for safety).
+            return true;
+        }
+        if (mSystemServicesReady) {
+            try {
+                // We allow all whitelisted apps, including those that don't want to be whitelisted
+                // for idle mode, because app idle (aka app standby) is really not as big an issue
+                // for controlling who participates vs. doze mode.
+                if (mInjector.isPowerSaveWhitelistExceptIdleApp(packageName)) {
+                    return true;
+                }
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+
+            if (isActiveDeviceAdmin(packageName, userId)) {
+                return true;
+            }
+
+            if (isActiveNetworkScorer(packageName)) {
+                return true;
+            }
+
+            if (mAppWidgetManager != null
+                    && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) {
+                return true;
+            }
+
+            if (isDeviceProvisioningPackage(packageName)) {
+                return true;
+            }
+        }
+
+        // Check this last, as it can be the most expensive check
+        if (isCarrierApp(packageName)) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Checks if an app has been idle for a while and filters out apps that are excluded.
      * It returns false if the current system state allows all apps to be considered active.
@@ -515,61 +639,11 @@
      */
     boolean isAppIdleFiltered(String packageName, int appId, int userId,
             long elapsedRealtime) {
-        if (packageName == null) return false;
-        // If not enabled at all, of course nobody is ever idle.
-        if (!mAppIdleEnabled) {
+        if (isAppSpecial(packageName, appId, userId)) {
             return false;
+        } else {
+            return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
         }
-        if (appId < Process.FIRST_APPLICATION_UID) {
-            // System uids never go idle.
-            return false;
-        }
-        if (packageName.equals("android")) {
-            // Nor does the framework (which should be redundant with the above, but for MR1 we will
-            // retain this for safety).
-            return false;
-        }
-        if (mSystemServicesReady) {
-            try {
-                // We allow all whitelisted apps, including those that don't want to be whitelisted
-                // for idle mode, because app idle (aka app standby) is really not as big an issue
-                // for controlling who participates vs. doze mode.
-                if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
-                    return false;
-                }
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-
-            if (isActiveDeviceAdmin(packageName, userId)) {
-                return false;
-            }
-
-            if (isActiveNetworkScorer(packageName)) {
-                return false;
-            }
-
-            if (mAppWidgetManager != null
-                    && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
-                return false;
-            }
-
-            if (isDeviceProvisioningPackage(packageName)) {
-                return false;
-            }
-        }
-
-        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
-            return false;
-        }
-
-        // Check this last, as it is the most expensive check
-        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
-        if (isCarrierApp(packageName)) {
-            return false;
-        }
-
-        return true;
     }
 
     int[] getIdleUidsForUser(int userId) {
@@ -577,7 +651,7 @@
             return new int[0];
         }
 
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
 
         List<ApplicationInfo> apps;
         try {
@@ -613,7 +687,7 @@
             }
         }
         if (DEBUG) {
-            Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
+            Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime));
         }
         int numIdle = 0;
         for (int i = uidStates.size() - 1; i >= 0; i--) {
@@ -643,6 +717,21 @@
                 .sendToTarget();
     }
 
+    @StandbyBuckets int getAppStandbyBucket(String packageName, int userId,
+            long elapsedRealtime, boolean shouldObfuscateInstantApps) {
+        if (shouldObfuscateInstantApps &&
+                mInjector.isPackageEphemeral(userId, packageName)) {
+            return AppStandby.STANDBY_BUCKET_ACTIVE;
+        }
+
+        return mAppIdleHistory.getAppStandbyBucket(packageName, userId, elapsedRealtime);
+    }
+
+    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+            String reason, long elapsedRealtime) {
+        mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason);
+    }
+
     private boolean isActiveDeviceAdmin(String packageName, int userId) {
         DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
         if (dpm == null) return false;
@@ -662,7 +751,7 @@
     private boolean isCarrierApp(String packageName) {
         synchronized (mAppIdleLock) {
             if (!mHaveCarrierPrivilegedApps) {
-                fetchCarrierPrivilegedAppsLA();
+                fetchCarrierPrivilegedAppsLocked();
             }
             if (mCarrierPrivilegedApps != null) {
                 return mCarrierPrivilegedApps.contains(packageName);
@@ -682,7 +771,7 @@
     }
 
     @GuardedBy("mAppIdleLock")
-    private void fetchCarrierPrivilegedAppsLA() {
+    private void fetchCarrierPrivilegedAppsLocked() {
         TelephonyManager telephonyManager =
                 mContext.getSystemService(TelephonyManager.class);
         mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
@@ -693,20 +782,19 @@
     }
 
     private boolean isActiveNetworkScorer(String packageName) {
-        NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
-                Context.NETWORK_SCORE_SERVICE);
-        return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
+        String activeScorer = mInjector.getActiveNetworkScorer();
+        return packageName != null && packageName.equals(activeScorer);
     }
 
     void informListeners(String packageName, int userId, boolean isIdle) {
-        for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
             listener.onAppIdleStateChanged(packageName, userId, isIdle);
         }
     }
 
     void informParoleStateChanged() {
         final boolean paroled = isParoledOrCharging();
-        for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
             listener.onParoleStateChanged(paroled);
         }
     }
@@ -726,8 +814,7 @@
     }
 
     boolean isDisplayOn() {
-        return mDisplayManager
-                .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+        return mInjector.isDefaultDisplayOn();
     }
 
     void clearAppIdleForPackage(String packageName, int userId) {
@@ -755,7 +842,7 @@
 
     void initializeDefaultsForSystemApps(int userId) {
         Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
         List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
                 PackageManager.MATCH_DISABLED_COMPONENTS,
                 userId);
@@ -786,9 +873,9 @@
         }
     }
 
-    void dumpUser(IndentingPrintWriter idpw, int userId) {
+    void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) {
         synchronized (mAppIdleLock) {
-            mAppIdleHistory.dump(idpw, userId);
+            mAppIdleHistory.dump(idpw, userId, pkg);
         }
     }
 
@@ -828,6 +915,116 @@
         pw.print(" mLastAppIdleParoledTime=");
         TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
         pw.println();
+        pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
+        pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
+    }
+
+    /**
+     * Injector for interaction with external code. Override methods to provide a mock
+     * implementation for tests.
+     * onBootPhase() must be called with at least the PHASE_SYSTEM_SERVICES_READY
+     */
+    static class Injector {
+
+        private final Context mContext;
+        private final Looper mLooper;
+        private IDeviceIdleController mDeviceIdleController;
+        private IBatteryStats mBatteryStats;
+        private PackageManagerInternal mPackageManagerInternal;
+        private DisplayManager mDisplayManager;
+        int mBootPhase;
+
+        Injector(Context context, Looper looper) {
+            mContext = context;
+            mLooper = looper;
+        }
+
+        Context getContext() {
+            return mContext;
+        }
+
+        Looper getLooper() {
+            return mLooper;
+        }
+
+        void onBootPhase(int phase) {
+            if (phase == PHASE_SYSTEM_SERVICES_READY) {
+                mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+                        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+                mBatteryStats = IBatteryStats.Stub.asInterface(
+                        ServiceManager.getService(BatteryStats.SERVICE_NAME));
+                mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+                mDisplayManager = (DisplayManager) mContext.getSystemService(
+                        Context.DISPLAY_SERVICE);
+            }
+            mBootPhase = phase;
+        }
+
+        int getBootPhase() {
+            return mBootPhase;
+        }
+
+        /**
+         * Returns the elapsed realtime since the device started. Override this
+         * to control the clock.
+         * @return elapsed realtime
+         */
+        long elapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        long currentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        boolean isAppIdleEnabled() {
+            return mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_enableAutoPowerModes);
+        }
+
+        boolean isCharging() {
+            return mContext.getSystemService(BatteryManager.class).isCharging();
+        }
+
+        boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
+            return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
+        }
+
+        File getDataSystemDirectory() {
+            return Environment.getDataSystemDirectory();
+        }
+
+        void noteEvent(int event, String packageName, int uid) throws RemoteException {
+            mBatteryStats.noteEvent(event, packageName, uid);
+        }
+
+        boolean isPackageEphemeral(int userId, String packageName) {
+            return mPackageManagerInternal.isPackageEphemeral(userId, packageName);
+        }
+
+        int[] getRunningUserIds() throws RemoteException {
+            return ActivityManager.getService().getRunningUserIds();
+        }
+
+        boolean isDefaultDisplayOn() {
+            return mDisplayManager
+                    .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+        }
+
+        void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) {
+            mDisplayManager.registerDisplayListener(listener, handler);
+        }
+
+        String getActiveNetworkScorer() {
+            NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
+                    Context.NETWORK_SCORE_SERVICE);
+            return nsm.getActiveScorerPackage();
+        }
+
+        public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName,
+                int userId) {
+            return appWidgetManager.isBoundWidgetPackage(packageName, userId);
+        }
     }
 
     class AppStandbyHandler extends Handler {
@@ -839,6 +1036,10 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MSG_INFORM_LISTENERS:
+                    informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
+                    break;
+
                 case MSG_FORCE_IDLE_STATE:
                     forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
                     break;
@@ -911,7 +1112,7 @@
             if (displayId == Display.DEFAULT_DISPLAY) {
                 final boolean displayOn = isDisplayOn();
                 synchronized (mAppIdleLock) {
-                    mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
+                    mAppIdleHistory.updateDisplay(displayOn, mInjector.elapsedRealtime());
                 }
             }
         }
@@ -931,6 +1132,8 @@
         private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
         private static final String KEY_PAROLE_INTERVAL = "parole_interval";
         private static final String KEY_PAROLE_DURATION = "parole_duration";
+        private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds";
+        private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds";
 
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
@@ -969,7 +1172,7 @@
                         COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
 
                 mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
-                        COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
+                        COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
 
                 // Default: 24 hours between paroles
                 mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
@@ -979,9 +1182,35 @@
                         COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
                 mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
                         mAppIdleScreenThresholdMillis);
+
+                String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
+                mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
+                        SCREEN_TIME_THRESHOLDS);
+
+                String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null);
+                mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
+                        ELAPSED_TIME_THRESHOLDS);
+            }
+        }
+
+        long[] parseLongArray(String values, long[] defaults) {
+            if (values == null) return defaults;
+            if (values.isEmpty()) {
+                // Reset to defaults
+                return defaults;
+            } else {
+                String[] thresholds = values.split("/");
+                if (thresholds.length == THRESHOLD_BUCKETS.length) {
+                    long[] array = new long[THRESHOLD_BUCKETS.length];
+                    for (int i = 0; i < THRESHOLD_BUCKETS.length; i++) {
+                        array[i] = Long.parseLong(thresholds[i]);
+                    }
+                    return array;
+                } else {
+                    return defaults;
+                }
             }
         }
     }
-
 }
 
diff --git a/com/android/server/usage/UsageStatsService.java b/com/android/server/usage/UsageStatsService.java
index afafea1..44e6a6c 100644
--- a/com/android/server/usage/UsageStatsService.java
+++ b/com/android/server/usage/UsageStatsService.java
@@ -20,6 +20,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
+import android.app.usage.AppStandby;
 import android.app.usage.ConfigurationStats;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
@@ -469,8 +470,32 @@
     void dump(String[] args, PrintWriter pw) {
         synchronized (mLock) {
             IndentingPrintWriter idpw = new IndentingPrintWriter(pw, "  ");
-            ArraySet<String> argSet = new ArraySet<>();
-            argSet.addAll(Arrays.asList(args));
+
+            boolean checkin = false;
+            boolean history = false;
+            String pkg = null;
+
+            if (args != null) {
+                for (int i = 0; i < args.length; i++) {
+                    String arg = args[i];
+                    if ("--checkin".equals(arg)) {
+                        checkin = true;
+                    } else if ("--history".equals(arg)) {
+                        history = true;
+                    } else if ("history".equals(arg)) {
+                        history = true;
+                        break;
+                    } else if ("flush".equals(arg)) {
+                        flushToDiskLocked();
+                        pw.println("Flushed stats to disk");
+                        return;
+                    } else {
+                        // Anything else is a pkg to filter
+                        pkg = arg;
+                        break;
+                    }
+                }
+            }
 
             final int userCount = mUserState.size();
             for (int i = 0; i < userCount; i++) {
@@ -478,26 +503,23 @@
                 idpw.printPair("user", userId);
                 idpw.println();
                 idpw.increaseIndent();
-                if (argSet.contains("--checkin")) {
+                if (checkin) {
                     mUserState.valueAt(i).checkin(idpw);
                 } else {
-                    mUserState.valueAt(i).dump(idpw);
+                    mUserState.valueAt(i).dump(idpw, pkg);
                     idpw.println();
-                    if (args.length > 0) {
-                        if ("history".equals(args[0])) {
-                            mAppStandby.dumpHistory(idpw, userId);
-                        } else if ("flush".equals(args[0])) {
-                            flushToDiskLocked();
-                            pw.println("Flushed stats to disk");
-                        }
+                    if (history) {
+                        mAppStandby.dumpHistory(idpw, userId);
                     }
                 }
-                mAppStandby.dumpUser(idpw, userId);
+                mAppStandby.dumpUser(idpw, userId, pkg);
                 idpw.decreaseIndent();
             }
 
-            pw.println();
-            mAppStandby.dumpState(args, pw);
+            if (pkg == null) {
+                pw.println();
+                mAppStandby.dumpState(args, pw);
+            }
         }
     }
 
@@ -654,6 +676,55 @@
         }
 
         @Override
+        public int getAppStandbyBucket(String packageName, String callingPackage, int userId) {
+            if (!hasPermission(callingPackage)) {
+                throw new SecurityException("Don't have permission to query app standby bucket");
+            }
+
+            final int callingUid = Binder.getCallingUid();
+            try {
+                userId = ActivityManager.getService().handleIncomingUser(
+                        Binder.getCallingPid(), callingUid, userId, false, true,
+                        "getAppStandbyBucket", null);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+            final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(callingUid,
+                    userId);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mAppStandby.getAppStandbyBucket(packageName, userId,
+                        SystemClock.elapsedRealtime(), obfuscateInstantApps);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setAppStandbyBucket(String packageName,
+                int bucket, int userId) {
+            getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
+                    "No permission to change app standby state");
+
+            final int callingUid = Binder.getCallingUid();
+            try {
+                userId = ActivityManager.getService().handleIncomingUser(
+                        Binder.getCallingPid(), callingUid, userId, false, true,
+                        "setAppStandbyBucket", null);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mAppStandby.setAppStandbyBucket(packageName, userId, bucket,
+                        AppStandby.REASON_PREDICTED + ":" + callingUid,
+                        SystemClock.elapsedRealtime());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
         public void whitelistAppTemporarily(String packageName, long duration, int userId)
                 throws RemoteException {
             StringBuilder reason = new StringBuilder(32);
diff --git a/com/android/server/usage/UserUsageStatsService.java b/com/android/server/usage/UserUsageStatsService.java
index 0abbb82..0b10590 100644
--- a/com/android/server/usage/UserUsageStatsService.java
+++ b/com/android/server/usage/UserUsageStatsService.java
@@ -474,19 +474,19 @@
         mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
             @Override
             public boolean checkin(IntervalStats stats) {
-                printIntervalStats(pw, stats, false);
+                printIntervalStats(pw, stats, false, null);
                 return true;
             }
         });
     }
 
-    void dump(IndentingPrintWriter pw) {
+    void dump(IndentingPrintWriter pw, String pkg) {
         // This is not a check-in, only dump in-memory stats.
         for (int interval = 0; interval < mCurrentStats.length; interval++) {
             pw.print("In-memory ");
             pw.print(intervalToString(interval));
             pw.println(" stats");
-            printIntervalStats(pw, mCurrentStats[interval], true);
+            printIntervalStats(pw, mCurrentStats[interval], true, pkg);
         }
     }
 
@@ -505,7 +505,7 @@
     }
 
     void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
-            boolean prettyDates) {
+            boolean prettyDates, String pkg) {
         if (prettyDates) {
             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
                     stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
@@ -521,6 +521,9 @@
         final int pkgCount = pkgStats.size();
         for (int i = 0; i < pkgCount; i++) {
             final UsageStats usageStats = pkgStats.valueAt(i);
+            if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
+                continue;
+            }
             pw.printPair("package", usageStats.mPackageName);
             pw.printPair("totalTime",
                     formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
@@ -533,6 +536,9 @@
         pw.println("ChooserCounts");
         pw.increaseIndent();
         for (UsageStats usageStats : pkgStats.values()) {
+            if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
+                continue;
+            }
             pw.printPair("package", usageStats.mPackageName);
             if (usageStats.mChooserCounts != null) {
                 final int chooserCountSize = usageStats.mChooserCounts.size();
@@ -555,19 +561,22 @@
         }
         pw.decreaseIndent();
 
-        pw.println("configurations");
-        pw.increaseIndent();
-        final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
-        final int configCount = configStats.size();
-        for (int i = 0; i < configCount; i++) {
-            final ConfigurationStats config = configStats.valueAt(i);
-            pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
-            pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
-            pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
-            pw.printPair("count", config.mActivationCount);
-            pw.println();
+        if (pkg == null) {
+            pw.println("configurations");
+            pw.increaseIndent();
+            final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
+            final int configCount = configStats.size();
+            for (int i = 0; i < configCount; i++) {
+                final ConfigurationStats config = configStats.valueAt(i);
+                pw.printPair("config", Configuration.resourceQualifierString(
+                        config.mConfiguration));
+                pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
+                pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
+                pw.printPair("count", config.mActivationCount);
+                pw.println();
+            }
+            pw.decreaseIndent();
         }
-        pw.decreaseIndent();
 
         pw.println("events");
         pw.increaseIndent();
@@ -575,6 +584,9 @@
         final int eventCount = events != null ? events.size() : 0;
         for (int i = 0; i < eventCount; i++) {
             final UsageEvents.Event event = events.valueAt(i);
+            if (pkg != null && !pkg.equals(event.mPackage)) {
+                continue;
+            }
             pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
             pw.printPair("type", eventToString(event.mEventType));
             pw.printPair("package", event.mPackage);
diff --git a/com/android/server/utils/PriorityDump.java b/com/android/server/utils/PriorityDump.java
index 054f156..fb92c2b 100644
--- a/com/android/server/utils/PriorityDump.java
+++ b/com/android/server/utils/PriorityDump.java
@@ -16,12 +16,19 @@
 
 package com.android.server.utils;
 
+import android.annotation.IntDef;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
 
 /**
  * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
- * {@link #PRIORITY_ARG} argument.
+ * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments.
  * <p>
  * Typical usage:
  *
@@ -31,13 +38,25 @@
  private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
 
      @Override
-     public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
-       pw.println("Donuts in the box: 1");
+     public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+       if (asProto) {
+         ProtoOutputStream proto = new ProtoOutputStream(fd);
+         proto.write(SpringfieldProto.DONUTS, 1);
+         proto.flush();
+       } else {
+         pw.println("Donuts in the box: 1");
+       }
      }
 
      @Override
      public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
-       pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT");
+        if (asProto) {
+          ProtoOutputStream proto = new ProtoOutputStream(fd);
+          proto.write(SpringfieldProto.REACTOR_STATUS, DANGER_MELTDOWN_IMMINENT);
+          proto.flush();
+        } else {
+          pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT");
+        }
      }
   };
 
@@ -65,6 +84,9 @@
     $ adb shell dumpsys snpp --dump-priority NORMAL
     Nuclear reactor status: DANGER - MELTDOWN IMMINENT
 
+    $ adb shell dumpsys snpp --dump-priority CRITICAL --proto
+    //binary output
+
  * </code></pre>
  *
  *
@@ -85,95 +107,146 @@
 public final class PriorityDump {
 
     public static final String PRIORITY_ARG = "--dump-priority";
+    public static final String PROTO_ARG = "--proto";
 
     private PriorityDump() {
         throw new UnsupportedOperationException();
     }
 
+    /** Enum to switch through supported priority types */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({PRIORITY_TYPE_INVALID, PRIORITY_TYPE_CRITICAL, PRIORITY_TYPE_HIGH,
+            PRIORITY_TYPE_NORMAL})
+    private @interface PriorityType { }
+    private static final int PRIORITY_TYPE_INVALID = 0;
+    private static final int PRIORITY_TYPE_CRITICAL = 1;
+    private static final int PRIORITY_TYPE_HIGH = 2;
+    private static final int PRIORITY_TYPE_NORMAL = 3;
+
     /**
-     * Parses {@code} and call the proper {@link PriorityDumper} method when the first argument is
-     * {@code --dump-priority}, stripping the priority and its type.
+     * Parses {@code args} matching {@code --dump-priority} and/or {@code --proto}. The matching
+     * arguments are stripped.
+     * <p>
+     * If priority args are passed as an argument, it will call the appropriate method and if proto
+     * args are passed then the {@code asProto} flag is set.
      * <p>
      * For example, if called as {@code --dump-priority HIGH arg1 arg2 arg3}, it will call
-     * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}) </code>
+     * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}, false) </code>
      * <p>
      * If the {@code --dump-priority} is not set, it calls
-     * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[])} passing the whole
+     * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[], boolean)} passing the whole
      * {@code args} instead.
      */
     public static void dump(PriorityDumper dumper, FileDescriptor fd, PrintWriter pw,
             String[] args) {
-        if (args != null && args.length >= 2 && args[0].equals(PRIORITY_ARG)) {
-            final String priority = args[1];
-            switch (priority) {
-                case "CRITICAL": {
-                    dumper.dumpCritical(fd, pw, getStrippedArgs(args));
-                    return;
+        boolean asProto = false;
+        @PriorityType int priority = PRIORITY_TYPE_INVALID;
+
+        if (args == null) {
+            dumper.dump(fd, pw, args, asProto);
+            return;
+        }
+
+        String[] strippedArgs = new String[args.length];
+        int strippedCount = 0;
+        for (int argIndex = 0; argIndex < args.length; argIndex++) {
+            if (args[argIndex].equals(PROTO_ARG)) {
+                asProto = true;
+            } else if (args[argIndex].equals(PRIORITY_ARG)) {
+                if (argIndex + 1 < args.length) {
+                    argIndex++;
+                    priority = getPriorityType(args[argIndex]);
                 }
-                case "HIGH": {
-                    dumper.dumpHigh(fd, pw, getStrippedArgs(args));
-                    return;
-                }
-                case "NORMAL": {
-                    dumper.dumpNormal(fd, pw, getStrippedArgs(args));
-                    return;
-                }
+            } else {
+                strippedArgs[strippedCount++] = args[argIndex];
             }
         }
-        dumper.dump(fd, pw, args);
+
+        if (strippedCount < args.length) {
+            strippedArgs = Arrays.copyOf(strippedArgs, strippedCount);
+        }
+
+        switch (priority) {
+            case PRIORITY_TYPE_CRITICAL: {
+                dumper.dumpCritical(fd, pw, strippedArgs, asProto);
+                return;
+            }
+            case PRIORITY_TYPE_HIGH: {
+                dumper.dumpHigh(fd, pw, strippedArgs, asProto);
+                return;
+            }
+            case PRIORITY_TYPE_NORMAL: {
+                dumper.dumpNormal(fd, pw, strippedArgs, asProto);
+                return;
+            }
+            default: {
+                dumper.dump(fd, pw, strippedArgs, asProto);
+                return;
+            }
+        }
     }
 
     /**
-     * Gets an array without the {@code --dump-priority PRIORITY} prefix.
+     * Converts priority argument type to enum.
      */
-    private static String[] getStrippedArgs(String[] args) {
-        final String[] stripped = new String[args.length - 2];
-        System.arraycopy(args, 2, stripped, 0, stripped.length);
-        return stripped;
+    private static @PriorityType int getPriorityType(String arg) {
+        switch (arg) {
+            case "CRITICAL": {
+                return PRIORITY_TYPE_CRITICAL;
+            }
+            case "HIGH": {
+                return PRIORITY_TYPE_HIGH;
+            }
+            case "NORMAL": {
+                return PRIORITY_TYPE_NORMAL;
+            }
+        }
+        return PRIORITY_TYPE_INVALID;
     }
 
     /**
      * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
-     * {@link #PRIORITY_ARG} argument.
+     * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments.
      *
      * @hide
      */
-    public static interface PriorityDumper {
+    public interface PriorityDumper {
 
         /**
          * Dumps only the critical section.
          */
         @SuppressWarnings("unused")
-        default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+        default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                boolean asProto) {
         }
 
         /**
          * Dumps only the high-priority section.
          */
         @SuppressWarnings("unused")
-        default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) {
+        default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
         }
 
         /**
          * Dumps only the normal section.
          */
         @SuppressWarnings("unused")
-        default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+        default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
         }
 
         /**
          * Dumps all sections.
          * <p>
          * This method is called when
-         * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[])} is
-         * called without priority arguments. By default, it calls the 3 {@code dumpTYPE} methods,
-         * so sub-classes just need to implement the priority types they support.
+         * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[], boolean)}
+         * is called without priority arguments. By default, it calls the 3 {@code dumpTYPE}
+         * methods, so sub-classes just need to implement the priority types they support.
          */
         @SuppressWarnings("unused")
-        default void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            dumpCritical(fd, pw, args);
-            dumpHigh(fd, pw, args);
-            dumpNormal(fd, pw, args);
+        default void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            dumpCritical(fd, pw, args, asProto);
+            dumpHigh(fd, pw, args, asProto);
+            dumpNormal(fd, pw, args, asProto);
         }
     }
 }
diff --git a/com/android/server/wifi/FrameworkFacade.java b/com/android/server/wifi/FrameworkFacade.java
index 760ee69..2c16444 100644
--- a/com/android/server/wifi/FrameworkFacade.java
+++ b/com/android/server/wifi/FrameworkFacade.java
@@ -25,7 +25,7 @@
 import android.database.ContentObserver;
 import android.net.TrafficStats;
 import android.net.Uri;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.os.BatteryStats;
 import android.os.Handler;
 import android.os.IBinder;
@@ -137,9 +137,9 @@
         return TrafficStats.getRxPackets(iface);
     }
 
-    public IpManager makeIpManager(
-            Context context, String iface, IpManager.Callback callback) {
-        return new IpManager(context, iface, callback);
+    public IpClient makeIpClient(
+            Context context, String iface, IpClient.Callback callback) {
+        return new IpClient(context, iface, callback);
     }
 
     /**
diff --git a/com/android/server/wifi/HalDeviceManager.java b/com/android/server/wifi/HalDeviceManager.java
index d6009c7..7a1e8d9 100644
--- a/com/android/server/wifi/HalDeviceManager.java
+++ b/com/android/server/wifi/HalDeviceManager.java
@@ -396,43 +396,48 @@
     }
 
     /**
-     * Creates a IWifiRttController corresponding to the input interface. A direct match to the
-     * IWifiChip.createRttController() method.
+     * Creates a IWifiRttController. A direct match to the IWifiChip.createRttController() method.
      *
      * Returns the created IWifiRttController or a null on error.
      */
-    public IWifiRttController createRttController(IWifiIface boundIface) {
-        if (DBG) Log.d(TAG, "createRttController: boundIface(name)=" + getName(boundIface));
+    public IWifiRttController createRttController() {
+        if (DBG) Log.d(TAG, "createRttController");
         synchronized (mLock) {
             if (mWifi == null) {
-                Log.e(TAG, "createRttController: null IWifi -- boundIface(name)="
-                        + getName(boundIface));
+                Log.e(TAG, "createRttController: null IWifi");
                 return null;
             }
 
-            IWifiChip chip = getChip(boundIface);
-            if (chip == null) {
-                Log.e(TAG, "createRttController: null IWifiChip -- boundIface(name)="
-                        + getName(boundIface));
+            WifiChipInfo[] chipInfos = getAllChipInfo();
+            if (chipInfos == null) {
+                Log.e(TAG, "createRttController: no chip info found");
+                stopWifi(); // major error: shutting down
                 return null;
             }
 
-            Mutable<IWifiRttController> rttResp = new Mutable<>();
-            try {
-                chip.createRttController(boundIface,
-                        (WifiStatus status, IWifiRttController rtt) -> {
-                            if (status.code == WifiStatusCode.SUCCESS) {
-                                rttResp.value = rtt;
-                            } else {
-                                Log.e(TAG, "IWifiChip.createRttController failed: " + statusString(
-                                        status));
-                            }
-                        });
-            } catch (RemoteException e) {
-                Log.e(TAG, "IWifiChip.createRttController exception: " + e);
+            for (WifiChipInfo chipInfo : chipInfos) {
+                Mutable<IWifiRttController> rttResp = new Mutable<>();
+                try {
+                    chipInfo.chip.createRttController(null,
+                            (WifiStatus status, IWifiRttController rtt) -> {
+                                if (status.code == WifiStatusCode.SUCCESS) {
+                                    rttResp.value = rtt;
+                                } else {
+                                    Log.e(TAG,
+                                            "IWifiChip.createRttController failed: " + statusString(
+                                                    status));
+                                }
+                            });
+                } catch (RemoteException e) {
+                    Log.e(TAG, "IWifiChip.createRttController exception: " + e);
+                }
+                if (rttResp.value != null) {
+                    return rttResp.value;
+                }
             }
 
-            return rttResp.value;
+            Log.e(TAG, "createRttController: not available from any of the chips");
+            return null;
         }
     }
 
diff --git a/com/android/server/wifi/WifiConfigManager.java b/com/android/server/wifi/WifiConfigManager.java
index ba1695a..b610bd9 100644
--- a/com/android/server/wifi/WifiConfigManager.java
+++ b/com/android/server/wifi/WifiConfigManager.java
@@ -1010,7 +1010,7 @@
         }
 
         boolean newNetwork = (existingInternalConfig == null);
-        // This is needed to inform IpManager about any IP configuration changes.
+        // This is needed to inform IpClient about any IP configuration changes.
         boolean hasIpChanged =
                 newNetwork || WifiConfigurationUtil.hasIpChanged(
                         existingInternalConfig, newInternalConfig);
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index 35dec2e..eefd2f0 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -80,10 +80,6 @@
         return mInterfaceName;
     }
 
-    public WifiVendorHal getVendorHal() {
-        return mWifiVendorHal;
-    }
-
     /**
      * Enable verbose logging for all sub modules.
      */
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index 0276ff4..dacc2d4 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -61,7 +61,7 @@
 import android.net.NetworkUtils;
 import android.net.StaticIpConfiguration;
 import android.net.Uri;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.ScanSettings;
@@ -857,32 +857,6 @@
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
-     * @param wifiConfig SSID, security and channel details as
-     *        part of WifiConfiguration
-     * @param enabled true to enable and false to disable
-     */
-    @Override
-    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
-        enforceChangePermission();
-        mWifiPermissionsUtil.enforceTetherChangePermission(mContext);
-
-        mLog.info("setWifiApEnabled uid=% enable=%").c(Binder.getCallingUid()).c(enabled).flush();
-
-        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
-            throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user.");
-        }
-        // null wifiConfig is a meaningful input for CMD_SET_AP
-        if (wifiConfig == null || isValid(wifiConfig)) {
-            int mode = WifiManager.IFACE_IP_MODE_UNSPECIFIED;
-            SoftApModeConfiguration softApConfig = new SoftApModeConfiguration(mode, wifiConfig);
-            mWifiController.sendMessage(CMD_SET_AP, enabled ? 1 : 0, 0, softApConfig);
-        } else {
-            Slog.e(TAG, "Invalid WifiConfiguration");
-        }
-    }
-
-    /**
      * see {@link WifiManager#getWifiApState()}
      * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
      *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
@@ -2304,11 +2278,11 @@
             // WifiMetrics proto bytes were requested. Dump only these.
             mWifiStateMachine.updateWifiMetrics();
             mWifiMetrics.dump(fd, pw, args);
-        } else if (args != null && args.length > 0 && IpManager.DUMP_ARG.equals(args[0])) {
-            // IpManager dump was requested. Pass it along and take no further action.
-            String[] ipManagerArgs = new String[args.length - 1];
-            System.arraycopy(args, 1, ipManagerArgs, 0, ipManagerArgs.length);
-            mWifiStateMachine.dumpIpManager(fd, pw, ipManagerArgs);
+        } else if (args != null && args.length > 0 && IpClient.DUMP_ARG.equals(args[0])) {
+            // IpClient dump was requested. Pass it along and take no further action.
+            String[] ipClientArgs = new String[args.length - 1];
+            System.arraycopy(args, 1, ipClientArgs, 0, ipClientArgs.length);
+            mWifiStateMachine.dumpIpClient(fd, pw, ipClientArgs);
         } else if (args != null && args.length > 0 && WifiScoreReport.DUMP_ARG.equals(args[0])) {
             WifiScoreReport wifiScoreReport = mWifiStateMachine.getWifiScoreReport();
             if (wifiScoreReport != null) wifiScoreReport.dump(fd, pw, args);
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index b7152ac..8e13313 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -59,7 +59,7 @@
 import android.net.StaticIpConfiguration;
 import android.net.TrafficStats;
 import android.net.dhcp.DhcpClient;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.wifi.IApInterface;
 import android.net.wifi.IClientInterface;
 import android.net.wifi.RssiPacketCountInfo;
@@ -447,7 +447,7 @@
         return true;
     }
 
-    private final IpManager mIpManager;
+    private final IpClient mIpClient;
 
     // Channel for sending replies.
     private AsyncChannel mReplyChannel = new AsyncChannel();
@@ -705,7 +705,7 @@
     static final int CMD_GET_ALL_MATCHING_CONFIGS                       = BASE + 168;
 
     /**
-     * Used to handle messages bounced between WifiStateMachine and IpManager.
+     * Used to handle messages bounced between WifiStateMachine and IpClient.
      */
     static final int CMD_IPV4_PROVISIONING_SUCCESS                      = BASE + 200;
     static final int CMD_IPV4_PROVISIONING_FAILURE                      = BASE + 201;
@@ -959,8 +959,8 @@
         mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
         mLastSignalLevel = -1;
 
-        mIpManager = mFacade.makeIpManager(mContext, mInterfaceName, new IpManagerCallback());
-        mIpManager.setMulticastFilter(true);
+        mIpClient = mFacade.makeIpClient(mContext, mInterfaceName, new IpClientCallback());
+        mIpClient.setMulticastFilter(true);
 
         mNoNetworksPeriodicScan = mContext.getResources().getInteger(
                 R.integer.config_wifi_no_network_periodic_scan_interval);
@@ -1139,7 +1139,7 @@
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    class IpManagerCallback extends IpManager.Callback {
+    class IpClientCallback extends IpClient.Callback {
         @Override
         public void onPreDhcpAction() {
             sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION);
@@ -1202,10 +1202,10 @@
         }
     }
 
-    private void stopIpManager() {
+    private void stopIpClient() {
         /* Restore power save and suspend optimizations */
         handlePostDhcpSetup();
-        mIpManager.stop();
+        mIpClient.stop();
     }
 
     PendingIntent getPrivateBroadcast(String action, int requestCode) {
@@ -2109,14 +2109,14 @@
      * Start filtering Multicast v4 packets
      */
     public void startFilteringMulticastPackets() {
-        mIpManager.setMulticastFilter(true);
+        mIpClient.setMulticastFilter(true);
     }
 
     /**
      * Stop filtering Multicast v4 packets
      */
     public void stopFilteringMulticastPackets() {
-        mIpManager.setMulticastFilter(false);
+        mIpClient.setMulticastFilter(false);
     }
 
     /**
@@ -2232,8 +2232,8 @@
         }
     }
 
-    public void dumpIpManager(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mIpManager.dump(fd, pw, args);
+    public void dumpIpClient(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mIpClient.dump(fd, pw, args);
     }
 
     @Override
@@ -2280,7 +2280,7 @@
         pw.println();
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_USER_ACTION);
         mWifiDiagnostics.dump(fd, pw, args);
-        dumpIpManager(fd, pw, args);
+        dumpIpClient(fd, pw, args);
         if (mWifiConnectivityManager != null) {
             mWifiConnectivityManager.dump(fd, pw, args);
         } else {
@@ -3066,7 +3066,7 @@
             log("Link configuration changed for netId: " + mLastNetworkId
                     + " old: " + mLinkProperties + " new: " + newLp);
         }
-        // We own this instance of LinkProperties because IpManager passes us a copy.
+        // We own this instance of LinkProperties because IpClient passes us a copy.
         mLinkProperties = newLp;
         if (mNetworkAgent != null) {
             mNetworkAgent.sendLinkProperties(mLinkProperties);
@@ -3096,7 +3096,7 @@
      */
     private void clearLinkProperties() {
         // Clear the link properties obtained from DHCP. The only caller of this
-        // function has already called IpManager#stop(), which clears its state.
+        // function has already called IpClient#stop(), which clears its state.
         synchronized (mDhcpResultsLock) {
             if (mDhcpResults != null) {
                 mDhcpResults.clear();
@@ -3322,7 +3322,7 @@
 
         clearTargetBssid("handleNetworkDisconnect");
 
-        stopIpManager();
+        stopIpClient();
 
         /* Reset data structures */
         mWifiScoreReport.reset();
@@ -3453,7 +3453,7 @@
                 // short in two ways:
                 // - at the time of the CMD_IP_CONFIGURATION_SUCCESSFUL event, we don't know if we
                 //   actually have ARP reachability. it might be better to wait until the wifi
-                //   network has been validated by IpManager.
+                //   network has been validated by IpClient.
                 // - in the case of a roaming event (intra-SSID), we probably trigger when L2 is
                 //   complete.
                 //
@@ -4378,7 +4378,7 @@
             // Disable legacy multicast filtering, which on some chipsets defaults to enabled.
             // Legacy IPv6 multicast filtering blocks ICMPv6 router advertisements which breaks IPv6
             // provisioning. Legacy IPv4 multicast filtering may be re-enabled later via
-            // IpManager.Callback.setFallbackMulticastFilter()
+            // IpClient.Callback.setFallbackMulticastFilter()
             mWifiNative.stopFilteringMulticastV4Packets();
             mWifiNative.stopFilteringMulticastV6Packets();
 
@@ -5043,7 +5043,7 @@
                     // DNAv4/DNAv6 -style probing for on-link neighbors of
                     // interest (e.g. routers); harmless if none are configured.
                     if (state == SupplicantState.COMPLETED) {
-                        mIpManager.confirmConfiguration();
+                        mIpClient.confirmConfiguration();
                     }
                     break;
                 case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
@@ -5314,7 +5314,7 @@
                         } else {
                             if (result.hasProxyChanged()) {
                                 log("Reconfiguring proxy on connection");
-                                mIpManager.setHttpProxy(
+                                mIpClient.setHttpProxy(
                                         getCurrentWifiConfiguration().getHttpProxy());
                             }
                             if (result.hasIpChanged()) {
@@ -5783,7 +5783,7 @@
 
         @Override
         public void exit() {
-            mIpManager.stop();
+            mIpClient.stop();
 
             // This is handled by receiving a NETWORK_DISCONNECTION_EVENT in ConnectModeState
             // Bug: 15347363
@@ -5813,17 +5813,17 @@
                     handlePreDhcpSetup();
                     break;
                 case DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE:
-                    mIpManager.completedPreDhcpAction();
+                    mIpClient.completedPreDhcpAction();
                     break;
                 case DhcpClient.CMD_POST_DHCP_ACTION:
                     handlePostDhcpSetup();
-                    // We advance to mConnectedState because IpManager will also send a
+                    // We advance to mConnectedState because IpClient will also send a
                     // CMD_IPV4_PROVISIONING_SUCCESS message, which calls handleIPv4Success(),
                     // which calls updateLinkProperties, which then sends
                     // CMD_IP_CONFIGURATION_SUCCESSFUL.
                     //
                     // In the event of failure, we transition to mDisconnectingState
-                    // similarly--via messages sent back from IpManager.
+                    // similarly--via messages sent back from IpClient.
                     break;
                 case CMD_IPV4_PROVISIONING_SUCCESS: {
                     handleIPv4Success((DhcpResults) message.obj);
@@ -6065,7 +6065,7 @@
             // cause the roam to fail and the device to disconnect.
             clearTargetBssid("ObtainingIpAddress");
 
-            // Stop IpManager in case we're switching from DHCP to static
+            // Stop IpClient in case we're switching from DHCP to static
             // configuration or vice versa.
             //
             // TODO: Only ever enter this state the first time we connect to a
@@ -6075,15 +6075,15 @@
             // disconnected, because DHCP might take a long time during which
             // connectivity APIs such as getActiveNetworkInfo should not return
             // CONNECTED.
-            stopIpManager();
+            stopIpClient();
 
-            mIpManager.setHttpProxy(currentConfig.getHttpProxy());
+            mIpClient.setHttpProxy(currentConfig.getHttpProxy());
             if (!TextUtils.isEmpty(mTcpBufferSizes)) {
-                mIpManager.setTcpBufferSizes(mTcpBufferSizes);
+                mIpClient.setTcpBufferSizes(mTcpBufferSizes);
             }
-            final IpManager.ProvisioningConfiguration prov;
+            final IpClient.ProvisioningConfiguration prov;
             if (!isUsingStaticIp) {
-                prov = IpManager.buildProvisioningConfiguration()
+                prov = IpClient.buildProvisioningConfiguration()
                             .withPreDhcpAction()
                             .withApfCapabilities(mWifiNative.getApfCapabilities())
                             .withNetwork(getCurrentNetwork())
@@ -6091,14 +6091,14 @@
                             .build();
             } else {
                 StaticIpConfiguration staticIpConfig = currentConfig.getStaticIpConfiguration();
-                prov = IpManager.buildProvisioningConfiguration()
+                prov = IpClient.buildProvisioningConfiguration()
                             .withStaticConfiguration(staticIpConfig)
                             .withApfCapabilities(mWifiNative.getApfCapabilities())
                             .withNetwork(getCurrentNetwork())
                             .withDisplayName(currentConfig.SSID)
                             .build();
             }
-            mIpManager.startProvisioning(prov);
+            mIpClient.startProvisioning(prov);
             // Get Link layer stats so as we get fresh tx packet counters
             getWifiLinkLayerStats();
         }
@@ -6294,10 +6294,10 @@
                         // We used to transition to ObtainingIpState in an
                         // attempt to do DHCPv4 RENEWs on framework roams.
                         // DHCP can take too long to time out, and we now rely
-                        // upon IpManager's use of IpReachabilityMonitor to
+                        // upon IpClient's use of IpReachabilityMonitor to
                         // confirm our current network configuration.
                         //
-                        // mIpManager.confirmConfiguration() is called within
+                        // mIpClient.confirmConfiguration() is called within
                         // the handling of SupplicantState.COMPLETED.
                         transitionTo(mConnectedState);
                     } else {
diff --git a/com/android/server/wifi/WifiVendorHal.java b/com/android/server/wifi/WifiVendorHal.java
index e0467d7..cec36a5 100644
--- a/com/android/server/wifi/WifiVendorHal.java
+++ b/com/android/server/wifi/WifiVendorHal.java
@@ -238,11 +238,6 @@
         clearState();
     }
 
-    // TODO: b/65014872 remove - have RttService (RTT2) interact directly with HalDeviceManager
-    public IWifiRttController getRttController() {
-        return mIWifiRttController;
-    }
-
     private WifiNative.VendorHalDeathEventHandler mDeathEventHandler;
 
     /**
@@ -312,7 +307,7 @@
                 if (!registerStaIfaceCallback()) {
                     return startFailedTo("register sta iface callback");
                 }
-                mIWifiRttController = mHalDeviceManager.createRttController(iface);
+                mIWifiRttController = mHalDeviceManager.createRttController();
                 if (mIWifiRttController == null) {
                     return startFailedTo("create RTT controller");
                 }
diff --git a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index d70ce41..fa16253 100644
--- a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -32,7 +32,7 @@
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
 import android.net.NetworkUtils;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.net.wifi.p2p.WifiP2pConfig;
@@ -113,7 +113,7 @@
     private Context mContext;
 
     INetworkManagementService mNwService;
-    private IpManager mIpManager;
+    private IpClient mIpClient;
     private DhcpResults mDhcpResults;
 
     private P2pStateMachine mP2pStateMachine;
@@ -180,12 +180,12 @@
     //   msg.obj  = StateMachine to send to when blocked
     public static final int BLOCK_DISCOVERY                 =   BASE + 15;
 
-    // Messages for interaction with IpManager.
-    private static final int IPM_PRE_DHCP_ACTION            =   BASE + 30;
-    private static final int IPM_POST_DHCP_ACTION           =   BASE + 31;
-    private static final int IPM_DHCP_RESULTS               =   BASE + 32;
-    private static final int IPM_PROVISIONING_SUCCESS       =   BASE + 33;
-    private static final int IPM_PROVISIONING_FAILURE       =   BASE + 34;
+    // Messages for interaction with IpClient.
+    private static final int IPC_PRE_DHCP_ACTION            =   BASE + 30;
+    private static final int IPC_POST_DHCP_ACTION           =   BASE + 31;
+    private static final int IPC_DHCP_RESULTS               =   BASE + 32;
+    private static final int IPC_PROVISIONING_SUCCESS       =   BASE + 33;
+    private static final int IPC_PROVISIONING_FAILURE       =   BASE + 34;
 
     public static final int ENABLED                         = 1;
     public static final int DISABLED                        = 0;
@@ -442,50 +442,50 @@
         }
     }
 
-    private void stopIpManager() {
-        if (mIpManager != null) {
-            mIpManager.stop();
-            mIpManager = null;
+    private void stopIpClient() {
+        if (mIpClient != null) {
+            mIpClient.stop();
+            mIpClient = null;
         }
         mDhcpResults = null;
     }
 
-    private void startIpManager(String ifname) {
-        stopIpManager();
+    private void startIpClient(String ifname) {
+        stopIpClient();
 
-        mIpManager = new IpManager(mContext, ifname,
-                new IpManager.Callback() {
+        mIpClient = new IpClient(mContext, ifname,
+                new IpClient.Callback() {
                     @Override
                     public void onPreDhcpAction() {
-                        mP2pStateMachine.sendMessage(IPM_PRE_DHCP_ACTION);
+                        mP2pStateMachine.sendMessage(IPC_PRE_DHCP_ACTION);
                     }
                     @Override
                     public void onPostDhcpAction() {
-                        mP2pStateMachine.sendMessage(IPM_POST_DHCP_ACTION);
+                        mP2pStateMachine.sendMessage(IPC_POST_DHCP_ACTION);
                     }
                     @Override
                     public void onNewDhcpResults(DhcpResults dhcpResults) {
-                        mP2pStateMachine.sendMessage(IPM_DHCP_RESULTS, dhcpResults);
+                        mP2pStateMachine.sendMessage(IPC_DHCP_RESULTS, dhcpResults);
                     }
                     @Override
                     public void onProvisioningSuccess(LinkProperties newLp) {
-                        mP2pStateMachine.sendMessage(IPM_PROVISIONING_SUCCESS);
+                        mP2pStateMachine.sendMessage(IPC_PROVISIONING_SUCCESS);
                     }
                     @Override
                     public void onProvisioningFailure(LinkProperties newLp) {
-                        mP2pStateMachine.sendMessage(IPM_PROVISIONING_FAILURE);
+                        mP2pStateMachine.sendMessage(IPC_PROVISIONING_FAILURE);
                     }
                 },
                 mNwService);
 
-        final IpManager.ProvisioningConfiguration config =
-                mIpManager.buildProvisioningConfiguration()
-                          .withoutIPv6()
-                          .withoutIpReachabilityMonitor()
-                          .withPreDhcpAction(30 * 1000)
-                          .withProvisioningTimeoutMs(36 * 1000)
-                          .build();
-        mIpManager.startProvisioning(config);
+        final IpClient.ProvisioningConfiguration config =
+                mIpClient.buildProvisioningConfiguration()
+                         .withoutIPv6()
+                         .withoutIpReachabilityMonitor()
+                         .withPreDhcpAction(30 * 1000)
+                         .withProvisioningTimeoutMs(36 * 1000)
+                         .build();
+        mIpClient.startProvisioning(config);
     }
 
     /**
@@ -642,10 +642,10 @@
         pw.println("mDeathDataByBinder " + mDeathDataByBinder);
         pw.println();
 
-        final IpManager ipManager = mIpManager;
-        if (ipManager != null) {
-            pw.println("mIpManager:");
-            ipManager.dump(fd, pw, args);
+        final IpClient ipClient = mIpClient;
+        if (ipClient != null) {
+            pw.println("mIpClient:");
+            ipClient.dump(fd, pw, args);
         }
     }
 
@@ -945,11 +945,11 @@
                     case DROP_WIFI_USER_REJECT:
                     case GROUP_CREATING_TIMED_OUT:
                     case DISABLE_P2P_TIMED_OUT:
-                    case IPM_PRE_DHCP_ACTION:
-                    case IPM_POST_DHCP_ACTION:
-                    case IPM_DHCP_RESULTS:
-                    case IPM_PROVISIONING_SUCCESS:
-                    case IPM_PROVISIONING_FAILURE:
+                    case IPC_PRE_DHCP_ACTION:
+                    case IPC_POST_DHCP_ACTION:
+                    case IPC_DHCP_RESULTS:
+                    case IPC_PROVISIONING_SUCCESS:
+                    case IPC_PROVISIONING_FAILURE:
                     case WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT:
                     case SET_MIRACAST_MODE:
                     case WifiP2pManager.START_LISTEN:
@@ -1958,7 +1958,7 @@
                             startDhcpServer(mGroup.getInterface());
                         } else {
                             mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
-                            startIpManager(mGroup.getInterface());
+                            startIpClient(mGroup.getInterface());
                             WifiP2pDevice groupOwner = mGroup.getOwner();
                             WifiP2pDevice peer = mPeers.get(groupOwner.deviceAddress);
                             if (peer != null) {
@@ -2218,17 +2218,17 @@
                             loge("Disconnect on unknown device: " + device);
                         }
                         break;
-                    case IPM_PRE_DHCP_ACTION:
+                    case IPC_PRE_DHCP_ACTION:
                         mWifiNative.setP2pPowerSave(mGroup.getInterface(), false);
-                        mIpManager.completedPreDhcpAction();
+                        mIpClient.completedPreDhcpAction();
                         break;
-                    case IPM_POST_DHCP_ACTION:
+                    case IPC_POST_DHCP_ACTION:
                         mWifiNative.setP2pPowerSave(mGroup.getInterface(), true);
                         break;
-                    case IPM_DHCP_RESULTS:
+                    case IPC_DHCP_RESULTS:
                         mDhcpResults = (DhcpResults) message.obj;
                         break;
-                    case IPM_PROVISIONING_SUCCESS:
+                    case IPC_PROVISIONING_SUCCESS:
                         if (DBG) logd("mDhcpResults: " + mDhcpResults);
                         if (mDhcpResults != null) {
                             setWifiP2pInfoOnGroupFormation(mDhcpResults.serverAddress);
@@ -2244,7 +2244,7 @@
                             loge("Failed to add iface to local network " + e);
                         }
                         break;
-                    case IPM_PROVISIONING_FAILURE:
+                    case IPC_PROVISIONING_FAILURE:
                         loge("IP provisioning failed");
                         mWifiNative.p2pGroupRemove(mGroup.getInterface());
                         break;
@@ -3061,8 +3061,8 @@
             if (mGroup.isGroupOwner()) {
                 stopDhcpServer(mGroup.getInterface());
             } else {
-                if (DBG) logd("stop IpManager");
-                stopIpManager();
+                if (DBG) logd("stop IpClient");
+                stopIpClient();
                 try {
                     mNwService.removeInterfaceFromLocalNetwork(mGroup.getInterface());
                 } catch (RemoteException e) {
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
index 21aa1ce..f731c19 100644
--- a/com/android/server/wifi/rtt/RttNative.java
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -34,8 +34,6 @@
 import android.util.Log;
 
 import com.android.server.wifi.HalDeviceManager;
-import com.android.server.wifi.WifiNative;
-import com.android.server.wifi.WifiVendorHal;
 import com.android.server.wifi.util.NativeUtil;
 
 import java.io.FileDescriptor;
@@ -52,15 +50,55 @@
 
     private final RttServiceImpl mRttService;
     private final HalDeviceManager mHalDeviceManager;
-    private final WifiVendorHal mWifiVendorHal;
 
-    private boolean mIsInitialized = false;
+    private Object mLock = new Object();
 
-    public RttNative(RttServiceImpl rttService, HalDeviceManager halDeviceManager,
-            WifiNative wifiNative) {
+    private IWifiRttController mIWifiRttController;
+
+    public RttNative(RttServiceImpl rttService, HalDeviceManager halDeviceManager) {
         mRttService = rttService;
         mHalDeviceManager = halDeviceManager;
-        mWifiVendorHal = wifiNative.getVendorHal();
+    }
+
+    /**
+     * Initialize the object - registering with the HAL device manager.
+     */
+    public void start() {
+        synchronized (mLock) {
+            mHalDeviceManager.initialize();
+            mHalDeviceManager.registerStatusListener(() -> {
+                if (VDBG) Log.d(TAG, "hdm.onStatusChanged");
+                updateController();
+            }, null);
+            updateController();
+        }
+    }
+
+    private void updateController() {
+        if (VDBG) Log.v(TAG, "updateController: mIWifiRttController=" + mIWifiRttController);
+
+        // only care about isStarted (Wi-Fi started) not isReady - since if not
+        // ready then Wi-Fi will also be down.
+        synchronized (mLock) {
+            if (mHalDeviceManager.isStarted()) {
+                if (mIWifiRttController == null) {
+                    mIWifiRttController = mHalDeviceManager.createRttController();
+                    if (mIWifiRttController == null) {
+                        Log.e(TAG, "updateController: Failed creating RTT controller - but Wifi is "
+                                + "started!");
+                    } else {
+                        try {
+                            mIWifiRttController.registerEventCallback(this);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "updateController: exception registering callback: " + e);
+                            mIWifiRttController = null;
+                        }
+                    }
+                }
+            } else {
+                mIWifiRttController = null;
+            }
+        }
     }
 
     /**
@@ -73,45 +111,62 @@
      */
     public boolean rangeRequest(int cmdId, RangingRequest request) {
         if (VDBG) Log.v(TAG, "rangeRequest: cmdId=" + cmdId + ", request=" + request);
-        // TODO: b/65014872 replace by direct access to HalDeviceManager
-        IWifiRttController rttController = mWifiVendorHal.getRttController();
-        if (rttController == null) {
-            Log.e(TAG, "rangeRequest: RttController is null");
-            return false;
-        }
-        if (!mIsInitialized) {
+        synchronized (mLock) {
+            if (mIWifiRttController == null) {
+                Log.e(TAG, "rangeRequest: RttController is null");
+                return false;
+            }
+
+            ArrayList<RttConfig> rttConfig = convertRangingRequestToRttConfigs(request);
+            if (rttConfig == null) {
+                Log.e(TAG, "rangeRequest: invalid request parameters");
+                return false;
+            }
+
             try {
-                WifiStatus status = rttController.registerEventCallback(this);
+                WifiStatus status = mIWifiRttController.rangeRequest(cmdId, rttConfig);
                 if (status.code != WifiStatusCode.SUCCESS) {
-                    Log.e(TAG,
-                            "rangeRequest: cannot register event callback -- code=" + status.code);
+                    Log.e(TAG, "rangeRequest: cannot issue range request -- code=" + status.code);
                     return false;
                 }
             } catch (RemoteException e) {
-                Log.e(TAG, "rangeRequest: exception registering callback: " + e);
+                Log.e(TAG, "rangeRequest: exception issuing range request: " + e);
                 return false;
             }
-            mIsInitialized = true;
-        }
 
-        ArrayList<RttConfig> rttConfig = convertRangingRequestToRttConfigs(request);
-        if (rttConfig == null) {
-            Log.e(TAG, "rangeRequest: invalid request parameters");
-            return false;
+            return true;
         }
+    }
 
-        try {
-            WifiStatus status = rttController.rangeRequest(cmdId, rttConfig);
-            if (status.code != WifiStatusCode.SUCCESS) {
-                Log.e(TAG, "rangeRequest: cannot issue range request -- code=" + status.code);
+    /**
+     * Cancel an outstanding ranging request: no guarantees of execution - we will ignore any
+     * results which are returned for the canceled request.
+     *
+     * @param cmdId The cmdId issued with the original rangeRequest command.
+     * @param macAddresses A list of MAC addresses for which to cancel the operation.
+     * @return Success status: true for success, false for failure.
+     */
+    public boolean rangeCancel(int cmdId, ArrayList<byte[]> macAddresses) {
+        if (VDBG) Log.v(TAG, "rangeCancel: cmdId=" + cmdId);
+        synchronized (mLock) {
+            if (mIWifiRttController == null) {
+                Log.e(TAG, "rangeCancel: RttController is null");
                 return false;
             }
-        } catch (RemoteException e) {
-            Log.e(TAG, "rangeRequest: exception issuing range request: " + e);
-            return false;
-        }
 
-        return true;
+            try {
+                WifiStatus status = mIWifiRttController.rangeCancel(cmdId, macAddresses);
+                if (status.code != WifiStatusCode.SUCCESS) {
+                    Log.e(TAG, "rangeCancel: cannot issue range cancel -- code=" + status.code);
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "rangeCancel: exception issuing range cancel: " + e);
+                return false;
+            }
+
+            return true;
+        }
     }
 
     /**
@@ -259,6 +314,7 @@
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("RttNative:");
-        pw.println("  mIsInitialized: " + mIsInitialized);
+        pw.println("  mHalDeviceManager: " + mHalDeviceManager);
+        pw.println("  mIWifiRttController: " + mIWifiRttController);
     }
 }
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
index f248f72..35f7615 100644
--- a/com/android/server/wifi/rtt/RttService.java
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -25,7 +25,6 @@
 import com.android.server.SystemService;
 import com.android.server.wifi.HalDeviceManager;
 import com.android.server.wifi.WifiInjector;
-import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 
 /**
@@ -61,13 +60,13 @@
 
             HalDeviceManager halDeviceManager = wifiInjector.getHalDeviceManager();
             HandlerThread handlerThread = wifiInjector.getRttHandlerThread();
-            WifiNative wifiNative = wifiInjector.getWifiNative();
             WifiPermissionsUtil wifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil();
 
             IWifiAwareManager awareBinder = (IWifiAwareManager) ServiceManager.getService(
                     Context.WIFI_AWARE_SERVICE);
 
-            RttNative rttNative = new RttNative(mImpl, halDeviceManager, wifiNative);
+            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 9a0a1af..1921047 100644
--- a/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.RttResult;
 import android.hardware.wifi.V1_0.RttStatus;
+import android.net.wifi.ScanResult;
 import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.IWifiAwareManager;
 import android.net.wifi.aware.PeerHandle;
@@ -33,8 +34,11 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.wifi.util.NativeUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 
@@ -63,6 +67,10 @@
 
     private RttServiceSynchronized mRttServiceSynchronized;
 
+    @VisibleForTesting
+    public static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout";
+
+    private static final long HAL_RANGING_TIMEOUT_MS = 5_000;
 
     public RttServiceImpl(Context context) {
         mContext = context;
@@ -204,11 +212,41 @@
         private RttNative mRttNative;
         private int mNextCommandId = 1000;
         private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>();
+        private WakeupMessage mRangingTimeoutMessage = null;
 
         RttServiceSynchronized(Looper looper, RttNative rttNative) {
             mRttNative = rttNative;
 
             mHandler = new Handler(looper);
+            mRangingTimeoutMessage = new WakeupMessage(mContext, mHandler,
+                    HAL_RANGING_TIMEOUT_TAG, () -> {
+                timeoutRangingRequest();
+            });
+        }
+
+        private void cancelRanging(RttRequestInfo rri) {
+            ArrayList<byte[]> macAddresses = new ArrayList<>();
+            for (RangingRequest.RttPeer peer : rri.request.mRttPeers) {
+                if (peer instanceof RangingRequest.RttPeerAp) {
+                    ScanResult scanResult =
+                            ((RangingRequest.RttPeerAp) peer).scanResult;
+
+                    byte[] addr = NativeUtil.macAddressToByteArray(scanResult.BSSID);
+                    if (addr.length != 6) {
+                        Log.e(TAG, "Invalid configuration: unexpected BSSID length -- "
+                                + peer);
+                        continue;
+                    }
+                    macAddresses.add(addr);
+                } else if (peer instanceof RangingRequest.RttPeerAware) {
+                    if (((RangingRequest.RttPeerAware) peer).peerMacAddress != null) {
+                        macAddresses.add(
+                                ((RangingRequest.RttPeerAware) peer).peerMacAddress);
+                    }
+                }
+            }
+
+            mRttNative.rangeCancel(rri.cmdId, macAddresses);
         }
 
         private void cleanUpOnClientDeath(int uid) {
@@ -220,12 +258,13 @@
             while (it.hasNext()) {
                 RttRequestInfo rri = it.next();
                 if (rri.uid == uid) {
-                    // TODO: actually abort operation - though API is not clear or clean
-                    if (rri.cmdId == 0) {
-                        // Until that happens we will get results for the last operation: which is
-                        // why we don't dispatch a new range request off the queue and keep the
-                        // currently running operation in the queue
+                    if (!rri.dispatchedToNative) {
                         it.remove();
+                    } else {
+                        Log.d(TAG, "Client death - cancelling RTT operation in progress: cmdId="
+                                + rri.cmdId);
+                        mRangingTimeoutMessage.cancel();
+                        cancelRanging(rri);
                     }
                 }
             }
@@ -236,6 +275,30 @@
             }
         }
 
+        private void timeoutRangingRequest() {
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.timeoutRangingRequest mRttRequestQueue="
+                        + mRttRequestQueue);
+            }
+            if (mRttRequestQueue.size() == 0) {
+                Log.w(TAG, "RttServiceSynchronized.timeoutRangingRequest: but nothing in queue!?");
+                return;
+            }
+            RttRequestInfo rri = mRttRequestQueue.get(0);
+            if (!rri.dispatchedToNative) {
+                Log.w(TAG, "RttServiceSynchronized.timeoutRangingRequest: command not dispatched "
+                        + "to native!?");
+                return;
+            }
+            cancelRanging(rri);
+            try {
+                rri.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RttServiceSynchronized.timeoutRangingRequest: callback failed: " + e);
+            }
+            executeNextRangingRequestIfPossible(true);
+        }
+
         private void queueRangingRequest(int uid, IBinder binder, IBinder.DeathRecipient dr,
                 String callingPackage, RangingRequest request, IRttCallback callback) {
             RttRequestInfo newRequest = new RttRequestInfo();
@@ -272,7 +335,7 @@
             }
 
             RttRequestInfo nextRequest = mRttRequestQueue.get(0);
-            if (nextRequest.cmdId != 0) {
+            if (nextRequest.peerHandlesTranslated || nextRequest.dispatchedToNative) {
                 if (VDBG) {
                     Log.v(TAG, "executeNextRangingRequestIfPossible: called but a command is "
                             + "executing. topOfQueue=" + nextRequest);
@@ -280,7 +343,6 @@
                 return;
             }
 
-            nextRequest.cmdId = mNextCommandId++;
             startRanging(nextRequest);
         }
 
@@ -297,7 +359,11 @@
                 return;
             }
 
-            if (!mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
+            nextRequest.cmdId = mNextCommandId++;
+            if (mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
+                mRangingTimeoutMessage.schedule(
+                        SystemClock.elapsedRealtime() + HAL_RANGING_TIMEOUT_MS);
+            } else {
                 Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
                 try {
                     nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
@@ -307,6 +373,7 @@
                 }
                 executeNextRangingRequestIfPossible(true);
             }
+            nextRequest.dispatchedToNative = true;
         }
 
         /**
@@ -402,6 +469,7 @@
                         + "pending!?");
                 return;
             }
+            mRangingTimeoutMessage.cancel();
             RttRequestInfo topOfQueueRequest = mRttRequestQueue.get(0);
 
             if (VDBG) {
@@ -520,10 +588,10 @@
         public IBinder.DeathRecipient dr;
         public String callingPackage;
         public RangingRequest request;
-        public byte[] mac;
         public IRttCallback callback;
 
         public int cmdId = 0; // uninitialized cmdId value
+        public boolean dispatchedToNative = false;
         public boolean peerHandlesTranslated = false;
 
         @Override
diff --git a/com/android/server/wifi/util/WifiPermissionsUtil.java b/com/android/server/wifi/util/WifiPermissionsUtil.java
index 069e5a8..d3c072f 100644
--- a/com/android/server/wifi/util/WifiPermissionsUtil.java
+++ b/com/android/server/wifi/util/WifiPermissionsUtil.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.net.ConnectivityManager;
 import android.net.NetworkScoreManager;
 import android.os.Binder;
 import android.os.RemoteException;
@@ -93,16 +92,6 @@
     }
 
     /**
-     * Check and enforce tether change permission.
-     *
-     * @param context Context object of the caller.
-     */
-    public void enforceTetherChangePermission(Context context) {
-        String pkgName = context.getOpPackageName();
-        ConnectivityManager.enforceTetherChangePermission(context, pkgName);
-    }
-
-    /**
      * Check and enforce Location permission.
      *
      * @param pkgName PackageName of the application requesting access
diff --git a/com/android/server/wm/AccessibilityController.java b/com/android/server/wm/AccessibilityController.java
index 6484a13..de4fd7c 100644
--- a/com/android/server/wm/AccessibilityController.java
+++ b/com/android/server/wm/AccessibilityController.java
@@ -74,12 +74,12 @@
  */
 final class AccessibilityController {
 
-    private final WindowManagerService mWindowManagerService;
+    private final WindowManagerService mService;
 
     private static final float[] sTempFloats = new float[9];
 
     public AccessibilityController(WindowManagerService service) {
-        mWindowManagerService = service;
+        mService = service;
     }
 
     private DisplayMagnifier mDisplayMagnifier;
@@ -91,7 +91,7 @@
             if (mDisplayMagnifier != null) {
                 throw new IllegalStateException("Magnification callbacks already set!");
             }
-            mDisplayMagnifier = new DisplayMagnifier(mWindowManagerService, callbacks);
+            mDisplayMagnifier = new DisplayMagnifier(mService, callbacks);
         } else {
             if  (mDisplayMagnifier == null) {
                 throw new IllegalStateException("Magnification callbacks already cleared!");
@@ -108,7 +108,7 @@
                         "Windows for accessibility callback already set!");
             }
             mWindowsForAccessibilityObserver = new WindowsForAccessibilityObserver(
-                    mWindowManagerService, callback);
+                    mService, callback);
         } else {
             if (mWindowsForAccessibilityObserver == null) {
                 throw new IllegalStateException(
@@ -120,7 +120,7 @@
 
     public void performComputeChangedWindowsNotLocked() {
         WindowsForAccessibilityObserver observer = null;
-        synchronized (mWindowManagerService) {
+        synchronized (mService) {
             observer = mWindowsForAccessibilityObserver;
         }
         if (observer != null) {
@@ -188,7 +188,7 @@
         // Not relevant for the display magnifier.
 
         WindowsForAccessibilityObserver observer = null;
-        synchronized (mWindowManagerService) {
+        synchronized (mService) {
             observer = mWindowsForAccessibilityObserver;
         }
         if (observer != null) {
@@ -268,7 +268,7 @@
         private final Region mTempRegion4 = new Region();
 
         private final Context mContext;
-        private final WindowManagerService mWindowManagerService;
+        private final WindowManagerService mService;
         private final MagnifiedViewport mMagnifedViewport;
         private final Handler mHandler;
 
@@ -281,9 +281,9 @@
         public DisplayMagnifier(WindowManagerService windowManagerService,
                 MagnificationCallbacks callbacks) {
             mContext = windowManagerService.mContext;
-            mWindowManagerService = windowManagerService;
+            mService = windowManagerService;
             mCallbacks = callbacks;
-            mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+            mHandler = new MyHandler(mService.mH.getLooper());
             mMagnifedViewport = new MagnifiedViewport();
             mLongAnimationDuration = mContext.getResources().getInteger(
                     com.android.internal.R.integer.config_longAnimTime);
@@ -292,7 +292,7 @@
         public void setMagnificationSpecLocked(MagnificationSpec spec) {
             mMagnifedViewport.updateMagnificationSpecLocked(spec);
             mMagnifedViewport.recomputeBoundsLocked();
-            mWindowManagerService.scheduleAnimationLocked();
+            mService.scheduleAnimationLocked();
         }
 
         public void setForceShowMagnifiableBoundsLocked(boolean show) {
@@ -330,7 +330,7 @@
                 Slog.i(LOG_TAG, "Layers changed.");
             }
             mMagnifedViewport.recomputeBoundsLocked();
-            mWindowManagerService.scheduleAnimationLocked();
+            mService.scheduleAnimationLocked();
         }
 
         public void onRotationChangedLocked(DisplayContent displayContent) {
@@ -421,7 +421,7 @@
         public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
             MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
             if (spec != null && !spec.isNop()) {
-                if (!mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
                     return null;
                 }
             }
@@ -565,7 +565,7 @@
                     portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
                     windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
 
-                    if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                    if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
                         mMagnificationRegion.op(windowBounds, Region.Op.UNION);
                         mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
                     } else {
@@ -632,7 +632,7 @@
                 if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) {
                     setMagnifiedRegionBorderShownLocked(false, false);
                     final long delay = (long) (mLongAnimationDuration
-                            * mWindowManagerService.getWindowAnimationScaleLocked());
+                            * mService.getWindowAnimationScaleLocked());
                     Message message = mHandler.obtainMessage(
                             MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
                     mHandler.sendMessageDelayed(message, delay);
@@ -675,7 +675,7 @@
             }
 
             private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
-                final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked();
+                final DisplayContent dc = mService.getDefaultDisplayContentLocked();
                 dc.forAllWindows((w) -> {
                     if (w.isOnScreen() && w.isVisibleLw()
                             && !w.mWinAnimator.mEnterAnimationPending) {
@@ -705,23 +705,25 @@
                     SurfaceControl surfaceControl = null;
                     try {
                         mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
-                        surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession,
-                                SURFACE_TITLE, mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT,
-                                SurfaceControl.HIDDEN);
+                        surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+                                .setName(SURFACE_TITLE)
+                                .setSize(mTempPoint.x, mTempPoint.y) // not a typo
+                                .setFormat(PixelFormat.TRANSLUCENT)
+                                .build();
                     } catch (OutOfResourcesException oore) {
                         /* ignore */
                     }
                     mSurfaceControl = surfaceControl;
                     mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
                             .getLayerStack());
-                    mSurfaceControl.setLayer(mWindowManagerService.mPolicy.getWindowLayerFromTypeLw(
+                    mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
                             TYPE_MAGNIFICATION_OVERLAY)
                             * WindowManagerService.TYPE_LAYER_MULTIPLIER);
                     mSurfaceControl.setPosition(0, 0);
                     mSurface.copyFrom(mSurfaceControl);
 
                     mAnimationController = new AnimationController(context,
-                            mWindowManagerService.mH.getLooper());
+                            mService.mH.getLooper());
 
                     TypedValue typedValue = new TypedValue();
                     context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
@@ -736,7 +738,7 @@
                 }
 
                 public void setShown(boolean shown, boolean animate) {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         if (mShown == shown) {
                             return;
                         }
@@ -751,13 +753,13 @@
                 @SuppressWarnings("unused")
                 // Called reflectively from an animator.
                 public int getAlpha() {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         return mAlpha;
                     }
                 }
 
                 public void setAlpha(int alpha) {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         if (mAlpha == alpha) {
                             return;
                         }
@@ -770,7 +772,7 @@
                 }
 
                 public void setBounds(Region bounds) {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         if (mBounds.equals(bounds)) {
                             return;
                         }
@@ -783,7 +785,7 @@
                 }
 
                 public void updateSize() {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
                         mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y);
                         invalidate(mDirtyRect);
@@ -797,12 +799,12 @@
                         mDirtyRect.setEmpty();
                     }
                     mInvalidated = true;
-                    mWindowManagerService.scheduleAnimationLocked();
+                    mService.scheduleAnimationLocked();
                 }
 
                 /** NOTE: This has to be called within a surface transaction. */
                 public void drawIfNeeded() {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         if (!mInvalidated) {
                             return;
                         }
@@ -950,11 +952,11 @@
                     } break;
 
                     case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
-                        synchronized (mWindowManagerService.mWindowMap) {
+                        synchronized (mService.mWindowMap) {
                             if (mMagnifedViewport.isMagnifyingLocked()
                                     || isForceShowingMagnifiableBoundsLocked()) {
                                 mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
-                                mWindowManagerService.scheduleAnimationLocked();
+                                mService.scheduleAnimationLocked();
                             }
                         }
                     } break;
@@ -995,7 +997,7 @@
 
         private final Context mContext;
 
-        private final WindowManagerService mWindowManagerService;
+        private final WindowManagerService mService;
 
         private final Handler mHandler;
 
@@ -1006,9 +1008,9 @@
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 WindowsForAccessibilityCallback callback) {
             mContext = windowManagerService.mContext;
-            mWindowManagerService = windowManagerService;
+            mService = windowManagerService;
             mCallback = callback;
-            mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+            mHandler = new MyHandler(mService.mH.getLooper());
             mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
                     .getSendRecurringAccessibilityEventsInterval();
             computeChangedWindows();
@@ -1034,11 +1036,11 @@
             boolean windowsChanged = false;
             List<WindowInfo> windows = new ArrayList<WindowInfo>();
 
-            synchronized (mWindowManagerService.mWindowMap) {
+            synchronized (mService.mWindowMap) {
                 // Do not send the windows if there is no current focus as
                 // the window manager is still looking for where to put it.
                 // We will do the work when we get a focus change callback.
-                if (mWindowManagerService.mCurrentFocus == null) {
+                if (mService.mCurrentFocus == null) {
                     return;
                 }
 
@@ -1320,7 +1322,7 @@
         }
 
         private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
-            final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked();
+            final DisplayContent dc = mService.getDefaultDisplayContentLocked();
             dc.forAllWindows((w) -> {
                 if (w.isVisibleLw()) {
                     outWindows.put(w.mLayer, w);
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index 5d03493..e873d32 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -1621,10 +1621,10 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
         writeNameToProto(proto, NAME);
-        super.writeToProto(proto, WINDOW_TOKEN);
+        super.writeToProto(proto, WINDOW_TOKEN, trim);
         proto.end(token);
     }
 
diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java
index d206554..9729e50 100644
--- a/com/android/server/wm/BlackFrame.java
+++ b/com/android/server/wm/BlackFrame.java
@@ -50,8 +50,11 @@
             int w = r-l;
             int h = b-t;
 
-            surface = new SurfaceControl(session, "BlackSurface",
-                    w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            surface = new SurfaceControl.Builder(session)
+                    .setName("BlackSurface")
+                    .setSize(w, h)
+                    .setColorLayer(true)
+                    .build();
 
             surface.setAlpha(1);
             surface.setLayerStack(layerStack);
diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java
index 85f468b..2d5d1b2 100644
--- a/com/android/server/wm/CircularDisplayMask.java
+++ b/com/android/server/wm/CircularDisplayMask.java
@@ -66,8 +66,11 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x,
-                    mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl = new SurfaceControl.Builder(session)
+                    .setName("CircularDisplayMask")
+                    .setSize(mScreenSize.x, mScreenSize.y) // not a typo
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
 
             ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(zOrder);
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
index a4ab3ba..cc94807 100644
--- a/com/android/server/wm/ConfigurationContainer.java
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -50,6 +50,9 @@
     /** Contains override configuration settings applied to this configuration container. */
     private Configuration mOverrideConfiguration = new Configuration();
 
+    /** True if mOverrideConfiguration is not empty */
+    private boolean mHasOverrideConfiguration;
+
     /**
      * Contains full configuration applied to this configuration container. Corresponds to full
      * parent's config with applied {@link #mOverrideConfiguration}.
@@ -101,6 +104,9 @@
      * @see #mFullConfiguration
      */
     public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        // Pre-compute this here, so we don't need to go through the entire Configuration when
+        // writing to proto (which has significant cost if we write a lot of empty configurations).
+        mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
         mOverrideConfiguration.setTo(overrideConfiguration);
         // Update full configuration of this container and all its children.
         final ConfigurationContainer parent = getParent();
@@ -330,18 +336,23 @@
      * Write to a protocol buffer output stream. Protocol buffer message definition is at
      * {@link com.android.server.wm.proto.ConfigurationContainerProto}.
      *
-     * @param protoOutputStream Stream to write the ConfigurationContainer object to.
-     * @param fieldId           Field Id of the ConfigurationContainer as defined in the parent
-     *                          message.
+     * @param proto    Stream to write the ConfigurationContainer object to.
+     * @param fieldId  Field Id of the ConfigurationContainer as defined in the parent
+     *                 message.
+     * @param trim     If true, reduce amount of data written.
      * @hide
      */
     @CallSuper
-    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
-        final long token = protoOutputStream.start(fieldId);
-        mOverrideConfiguration.writeToProto(protoOutputStream, OVERRIDE_CONFIGURATION);
-        mFullConfiguration.writeToProto(protoOutputStream, FULL_CONFIGURATION);
-        mMergedOverrideConfiguration.writeToProto(protoOutputStream, MERGED_OVERRIDE_CONFIGURATION);
-        protoOutputStream.end(token);
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
+        final long token = proto.start(fieldId);
+        if (!trim || mHasOverrideConfiguration) {
+            mOverrideConfiguration.writeToProto(proto, OVERRIDE_CONFIGURATION);
+        }
+        if (!trim) {
+            mFullConfiguration.writeToProto(proto, FULL_CONFIGURATION);
+            mMergedOverrideConfiguration.writeToProto(proto, MERGED_OVERRIDE_CONFIGURATION);
+        }
+        proto.end(token);
     }
 
     /**
diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java
index 48181d3..401547e 100644
--- a/com/android/server/wm/DimLayer.java
+++ b/com/android/server/wm/DimLayer.java
@@ -104,9 +104,11 @@
     private void constructSurface(WindowManagerService service) {
         service.openSurfaceTransaction();
         try {
-            mDimSurface = new SurfaceControl(service.mFxSession, mName,
-                    16, 16, PixelFormat.OPAQUE,
-                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            mDimSurface = new SurfaceControl.Builder(service.mFxSession)
+                    .setName(mName)
+                    .setSize(16, 16)
+                    .setColorLayer(true)
+                    .build();
 
             if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
                     "  DIM " + mDimSurface + ": CREATE");
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 03fdc96..4c6ab3f 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -2137,27 +2137,27 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         proto.write(ID, mDisplayId);
         for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
             final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
-            stack.writeToProto(proto, STACKS);
+            stack.writeToProto(proto, STACKS, trim);
         }
         mDividerControllerLocked.writeToProto(proto, DOCKED_STACK_DIVIDER_CONTROLLER);
         mPinnedStackControllerLocked.writeToProto(proto, PINNED_STACK_CONTROLLER);
         for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
             final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i);
-            windowToken.writeToProto(proto, ABOVE_APP_WINDOWS);
+            windowToken.writeToProto(proto, ABOVE_APP_WINDOWS, trim);
         }
         for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
             final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i);
-            windowToken.writeToProto(proto, BELOW_APP_WINDOWS);
+            windowToken.writeToProto(proto, BELOW_APP_WINDOWS, trim);
         }
         for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) {
             final WindowToken windowToken = mImeWindowsContainers.getChildAt(i);
-            windowToken.writeToProto(proto, IME_WINDOWS);
+            windowToken.writeToProto(proto, IME_WINDOWS, trim);
         }
         proto.write(DPI, mBaseDisplayDensity);
         mDisplayInfo.writeToProto(proto, DISPLAY_INFO);
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index 52526e2..5ce4d46 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -137,6 +137,8 @@
     float mLastDividerProgress;
     private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4];
     private boolean mImeHideRequested;
+    private final Rect mLastDimLayerRect = new Rect();
+    private float mLastDimLayerAlpha;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
@@ -525,7 +527,6 @@
      * display in that windowing mode.
      */
     void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
-        mService.openSurfaceTransaction();
         // TODO: Maybe only allow split-screen windowing modes?
         final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
                 ? mDisplayContent.getStack(targetWindowingMode)
@@ -535,16 +536,33 @@
         if (visibleAndValid) {
             stack.getDimBounds(mTmpRect);
             if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
-                mDimLayer.setBounds(mTmpRect);
-                mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
+                if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) {
+                    try {
+                        // TODO: This should use the regular animation transaction - here and below
+                        mService.openSurfaceTransaction();
+                        mDimLayer.setBounds(mTmpRect);
+                        mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
+                    } finally {
+                        mService.closeSurfaceTransaction();
+                    }
+                }
+                mLastDimLayerRect.set(mTmpRect);
+                mLastDimLayerAlpha = alpha;
             } else {
                 visibleAndValid = false;
             }
         }
         if (!visibleAndValid) {
-            mDimLayer.hide();
+            if (mLastDimLayerAlpha != 0f) {
+                try {
+                    mService.openSurfaceTransaction();
+                    mDimLayer.hide();
+                } finally {
+                    mService.closeSurfaceTransaction();
+                }
+            }
+            mLastDimLayerAlpha = 0f;
         }
-        mService.closeSurfaceTransaction();
     }
 
     /**
diff --git a/com/android/server/wm/DragDropController.java b/com/android/server/wm/DragDropController.java
new file mode 100644
index 0000000..a3c6167
--- /dev/null
+++ b/com/android/server/wm/DragDropController.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.content.ClipData;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Message;
+import android.util.Slog;
+import android.view.Display;
+import android.view.IWindow;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.View;
+import com.android.server.wm.WindowManagerService.H;
+
+/**
+ * Managing drag and drop operations initiated by View#startDragAndDrop.
+ */
+class DragDropController {
+    private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
+    private static final long DRAG_TIMEOUT_MS = 5000;
+    DragState mDragState;
+
+    boolean dragDropActiveLocked() {
+        return mDragState != null;
+    }
+
+    IBinder prepareDrag(WindowManagerService service, 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
+                    + " flags=" + Integer.toHexString(flags) + " win=" + window
+                    + " asbinder=" + window.asBinder());
+        }
+
+        IBinder token = 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 =
+                    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,
+            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) {
+                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;
+            }
+
+            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) {
+        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;
+            }
+
+            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?
+            }
+
+            mDragState.mDragResult = consumed;
+            mDragState.endDragLw();
+        }
+    }
+
+    void cancelDragAndDrop(WindowManagerService service, 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()");
+            }
+
+            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();
+        }
+    }
+
+    void dragRecipientEntered(IWindow window) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
+        }
+    }
+
+    void dragRecipientExited(IWindow window) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
+        }
+    }
+
+    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;
+            }
+
+            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;
+            }
+
+            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) {
+                        interceptor.tearDown();
+                    }
+                }
+                break;
+            }
+        }
+    }
+}
diff --git a/com/android/server/wm/DragState.java b/com/android/server/wm/DragState.java
index 3fdafc7..11f2241 100644
--- a/com/android/server/wm/DragState.java
+++ b/com/android/server/wm/DragState.java
@@ -22,6 +22,10 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.animation.Animator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Context;
@@ -29,7 +33,9 @@
 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;
@@ -44,16 +50,12 @@
 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.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
-import android.view.animation.ScaleAnimation;
 import android.view.animation.Transformation;
-import android.view.animation.TranslateAnimation;
 
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputWindowHandle;
@@ -78,8 +80,20 @@
             View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
             View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
 
+    // Property names for animations
+    private static final String ANIMATED_PROPERTY_X = "x";
+    private static final String ANIMATED_PROPERTY_Y = "y";
+    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;
     IBinder mToken;
+    /**
+     * Do not use the variable from the out of animation thread while mAnimator is not null.
+     */
     SurfaceControl mSurfaceControl;
     int mFlags;
     IBinder mLocalWin;
@@ -101,10 +115,10 @@
     boolean mDragInProgress;
     DisplayContent mDisplayContent;
 
-    private Animation mAnimation;
-    final Transformation mTransformation = new Transformation();
+    @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) {
@@ -114,9 +128,14 @@
         mFlags = flags;
         mLocalWin = localWin;
         mNotifiedWindows = new ArrayList<WindowState>();
+        mHandler = new DragStateHandler(service.mH.getLooper());
     }
 
     void reset() {
+        if (mAnimator != null) {
+            Slog.wtf(TAG_WM,
+                    "Unexpectedly destroying mSurfaceControl while animation is running");
+        }
         if (mSurfaceControl != null) {
             mSurfaceControl.destroy();
         }
@@ -388,23 +407,33 @@
     }
 
     void endDragLw() {
-        if (mAnimation != null) {
+        if (mAnimator != null) {
             return;
         }
         if (!mDragResult) {
-            mAnimation = createReturnAnimationLocked();
-            mService.scheduleAnimationLocked();
+            mAnimator = createReturnAnimationLocked();
             return;  // Will call cleanUpDragLw when the animation is done.
         }
         cleanUpDragLw();
     }
 
     void cancelDragLw() {
-        if (mAnimation != null) {
+        if (mAnimator != null) {
             return;
         }
-        mAnimation = createCancelAnimationLocked();
-        mService.scheduleAnimationLocked();
+        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;
+            return;
+        }
+        mAnimator = createCancelAnimationLocked();
     }
 
     private void cleanUpDragLw() {
@@ -418,11 +447,11 @@
 
         // free our resources and drop all the object references
         reset();
-        mService.mDragState = null;
+        mService.mDragDropController.mDragState = null;
     }
 
     void notifyMoveLw(float x, float y) {
-        if (mAnimation != null) {
+        if (mAnimator != null) {
             return;
         }
         mCurrentX = x;
@@ -491,7 +520,7 @@
     // dispatch the global drag-ended message, 'false' if we need to wait for a
     // result from the recipient.
     boolean notifyDropLw(float x, float y) {
-        if (mAnimation != null) {
+        if (mAnimator != null) {
             return false;
         }
         mCurrentX = x;
@@ -560,56 +589,52 @@
                 dragAndDropPermissions, result);
     }
 
-    boolean stepAnimationLocked(long currentTimeMs) {
-        if (mAnimation == null) {
-            return false;
-        }
+    private ValueAnimator createReturnAnimationLocked() {
+        final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
+                        mOriginalX - mThumbOffsetX),
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
+                        mOriginalY - mThumbOffsetY),
+                PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 1),
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
 
-        mTransformation.clear();
-        if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
-            cleanUpDragLw();
-            return false;
-        }
-
-        mTransformation.getMatrix().postTranslate(
-                mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY);
-        final float tmpFloats[] = mService.mTmpFloats;
-        mTransformation.getMatrix().getValues(tmpFloats);
-        mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
-        mSurfaceControl.setAlpha(mTransformation.getAlpha());
-        mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
-                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
-        return true;
-    }
-
-    private Animation createReturnAnimationLocked() {
-        final AnimationSet set = new AnimationSet(false);
         final float translateX = mOriginalX - mCurrentX;
         final float translateY = mOriginalY - mCurrentY;
-        set.addAnimation(new TranslateAnimation( 0, translateX, 0, translateY));
-        set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
         // Adjust the duration to the travel distance.
         final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY);
         final double displayDiagonal =
                 Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
         final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
                 * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
-        set.setDuration(duration);
-        set.setInterpolator(mCubicEaseOutInterpolator);
-        set.initialize(0, 0, 0, 0);
-        set.start();  // Will start on the first call to getTransformation.
-        return set;
+        final AnimationListener listener = new AnimationListener();
+        animator.setDuration(duration);
+        animator.setInterpolator(mCubicEaseOutInterpolator);
+        animator.addListener(listener);
+        animator.addUpdateListener(listener);
+
+        mService.mAnimationHandler.post(() -> animator.start());
+        return animator;
     }
 
-    private Animation createCancelAnimationLocked() {
-        final AnimationSet set = new AnimationSet(false);
-        set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY));
-        set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0));
-        set.setDuration(MIN_ANIMATION_DURATION_MS);
-        set.setInterpolator(mCubicEaseOutInterpolator);
-        set.initialize(0, 0, 0, 0);
-        set.start();  // Will start on the first call to getTransformation.
-        return set;
+    private ValueAnimator createCancelAnimationLocked() {
+        final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
+                PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 0),
+                PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+        final AnimationListener listener = new AnimationListener();
+        animator.setDuration(MIN_ANIMATION_DURATION_MS);
+        animator.setInterpolator(mCubicEaseOutInterpolator);
+        animator.addListener(listener);
+        animator.addUpdateListener(listener);
+
+        mService.mAnimationHandler.post(() -> animator.start());
+        return animator;
     }
 
     private boolean isFromSource(int source) {
@@ -622,4 +647,68 @@
             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
+        public void onAnimationUpdate(ValueAnimator animation) {
+            try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
+                transaction.setPosition(
+                        mSurfaceControl,
+                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X),
+                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y));
+                transaction.setAlpha(
+                        mSurfaceControl,
+                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
+                transaction.setMatrix(
+                        mSurfaceControl,
+                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
+                        0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
+                transaction.apply();
+            }
+        }
+
+        @Override
+        public void onAnimationStart(Animator animator) {}
+
+        @Override
+        public void onAnimationCancel(Animator animator) {}
+
+        @Override
+        public void onAnimationRepeat(Animator animator) {}
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            // Updating mDragState requires the WM lock so continues it on the out of
+            // AnimationThread.
+            mHandler.sendEmptyMessage(MSG_ANIMATION_END);
+        }
+    }
 }
diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java
index 19bd8e9..8bec8d7 100644
--- a/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -56,8 +56,11 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x,
-                    mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl = new SurfaceControl.Builder(session)
+                    .setName("EmulatorDisplayOverlay")
+                    .setSize(mScreenSize.x, mScreenSize.y)
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
             ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(zOrder);
             ctrl.setPosition(0, 0);
diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java
index 238cb9f..cf1f171 100644
--- a/com/android/server/wm/InputMonitor.java
+++ b/com/android/server/wm/InputMonitor.java
@@ -367,12 +367,13 @@
         // currently has touch focus.
 
         // If there's a drag in flight, provide a pseudo-window to catch drag input
-        final boolean inDrag = (mService.mDragState != null);
+        final boolean inDrag = mService.mDragDropController.dragDropActiveLocked();
         if (inDrag) {
             if (DEBUG_DRAG) {
                 Log.d(TAG_WM, "Inserting drag window");
             }
-            final InputWindowHandle dragWindowHandle = mService.mDragState.getInputWindowHandle();
+            final InputWindowHandle dragWindowHandle =
+                    mService.mDragDropController.mDragState.getInputWindowHandle();
             if (dragWindowHandle != null) {
                 addInputWindowHandle(dragWindowHandle);
             } else {
@@ -689,7 +690,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.mDragState.sendDragStartedIfNeededLw(w);
+                mService.mDragDropController.mDragState.sendDragStartedIfNeededLw(w);
             }
 
             addInputWindowHandle(
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index fd57470..a710441 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -1081,19 +1081,21 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         if (mService.mDisplayReady) {
             final int count = mChildren.size();
             for (int i = 0; i < count; ++i) {
                 final DisplayContent displayContent = mChildren.get(i);
-                displayContent.writeToProto(proto, DISPLAYS);
+                displayContent.writeToProto(proto, DISPLAYS, trim);
             }
         }
-        forAllWindows((w) -> {
-            w.writeIdentifierToProto(proto, WINDOWS);
-        }, true);
+        if (!trim) {
+            forAllWindows((w) -> {
+                w.writeIdentifierToProto(proto, WINDOWS);
+            }, true);
+        }
         proto.end(token);
     }
 
diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java
index 8e99be8..c25b19c 100644
--- a/com/android/server/wm/ScreenRotationAnimation.java
+++ b/com/android/server/wm/ScreenRotationAnimation.java
@@ -269,14 +269,11 @@
 
         try {
             try {
-                int flags = SurfaceControl.HIDDEN;
-                if (isSecure) {
-                    flags |= SurfaceControl.SECURE;
-                }
-
-                mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
-                        mWidth, mHeight,
-                        PixelFormat.OPAQUE, flags);
+                mSurfaceControl = new SurfaceControl.Builder(session)
+                        .setName("ScreenshotSurface")
+                        .setSize(mWidth, mHeight)
+                        .setSecure(isSecure)
+                        .build();
 
                 // capture a screenshot into the surface we just created
                 Surface sur = new Surface();
diff --git a/com/android/server/wm/Session.java b/com/android/server/wm/Session.java
index 4dd147e..717c577 100644
--- a/com/android/server/wm/Session.java
+++ b/com/android/server/wm/Session.java
@@ -68,9 +68,7 @@
  * This class represents an active client session.  There is generally one
  * Session object per process that is interacting with the window manager.
  */
-// Needs to be public and not final so we can mock during tests...sucks I know :(
-public class Session extends IWindowSession.Stub
-        implements IBinder.DeathRecipient {
+class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
     final WindowManagerService mService;
     final IWindowSessionCallback mCallback;
     final IInputMethodClient mClient;
@@ -83,6 +81,7 @@
     private final Set<WindowSurfaceController> mAppOverlaySurfaces = new HashSet<>();
     // Set of visible alert window surfaces connected to this session.
     private final Set<WindowSurfaceController> mAlertWindowSurfaces = new HashSet<>();
+    private final DragDropController mDragDropController;
     final boolean mCanAddInternalSystemWindow;
     final boolean mCanHideNonSystemOverlayWindows;
     final boolean mCanAcquireSleepToken;
@@ -108,6 +107,7 @@
         mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
                 == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
+        mDragDropController = mService.mDragDropController;
         StringBuilder sb = new StringBuilder();
         sb.append("Session{");
         sb.append(Integer.toHexString(System.identityHashCode(this)));
@@ -306,94 +306,56 @@
 
     /* Drag/drop */
     @Override
-    public IBinder prepareDrag(IWindow window, int flags,
-            int width, int height, Surface outSurface) {
-        return mService.prepareDragSurface(window, mSurfaceSession, flags,
-                width, height, outSurface);
+    public IBinder prepareDrag(IWindow window, int flags, int width, int height,
+            Surface outSurface) {
+        final int callerPid = Binder.getCallingPid();
+        final int callerUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mDragDropController.prepareDrag(
+                    mService, mSurfaceSession, callerPid, callerUid, window, flags, width, height,
+                    outSurface);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
     }
 
     @Override
     public 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);
+        return mDragDropController.performDrag(mService, window, dragToken, touchSource,
+                touchX, touchY, thumbCenterX, thumbCenterY, data);
+    }
+
+    @Override
+    public void reportDropResult(IWindow window, boolean consumed) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mDragDropController.reportDropResult(mService, window, consumed);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
+    }
 
-        synchronized (mService.mWindowMap) {
-            if (mService.mDragState == null) {
-                Slog.w(TAG_WM, "No drag prepared");
-                throw new IllegalStateException("performDrag() without prepareDrag()");
-            }
-
-            if (dragToken != mService.mDragState.mToken) {
-                Slog.w(TAG_WM, "Performing mismatched drag");
-                throw new IllegalStateException("performDrag() does not match prepareDrag()");
-            }
-
-            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()
-
-            mService.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) {
-               return false;
-            }
-            Display display = displayContent.getDisplay();
-            mService.mDragState.register(display);
-            if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
-                    mService.mDragState.getInputChannel())) {
-                Slog.e(TAG_WM, "Unable to transfer touch focus");
-                mService.mDragState.unregister();
-                mService.mDragState.reset();
-                mService.mDragState = null;
-                return false;
-            }
-
-            mService.mDragState.mDisplayContent = displayContent;
-            mService.mDragState.mData = data;
-            mService.mDragState.broadcastDragStartedLw(touchX, touchY);
-            mService.mDragState.overridePointerIconLw(touchSource);
-
-            // remember the thumb offsets for later
-            mService.mDragState.mThumbOffsetX = thumbCenterX;
-            mService.mDragState.mThumbOffsetY = thumbCenterY;
-
-            // Make the surface visible at the proper location
-            final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl;
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                    TAG_WM, ">>> OPEN TRANSACTION performDrag");
-            mService.openSurfaceTransaction();
-            try {
-                surfaceControl.setPosition(touchX - thumbCenterX,
-                        touchY - thumbCenterY);
-                surfaceControl.setLayer(mService.mDragState.getDragLayerLw());
-                surfaceControl.setLayerStack(display.getLayerStack());
-                surfaceControl.show();
-            } finally {
-                mService.closeSurfaceTransaction();
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                        TAG_WM, "<<< CLOSE TRANSACTION performDrag");
-            }
-
-            mService.mDragState.notifyLocationLw(touchX, touchY);
+    @Override
+    public void cancelDragAndDrop(IBinder dragToken) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mDragDropController.cancelDragAndDrop(mService, dragToken);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
+    }
 
-        return true;    // success!
+    @Override
+    public void dragRecipientEntered(IWindow window) {
+        mDragDropController.dragRecipientEntered(window);
+    }
+
+    @Override
+    public void dragRecipientExited(IWindow window) {
+        mDragDropController.dragRecipientExited(window);
     }
 
     @Override
@@ -410,90 +372,6 @@
     }
 
     @Override
-    public void reportDropResult(IWindow window, boolean consumed) {
-        IBinder token = window.asBinder();
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
-        }
-
-        synchronized (mService.mWindowMap) {
-            long ident = Binder.clearCallingIdentity();
-            try {
-                if (mService.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 (mService.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.
-                mService.mH.removeMessages(H.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?
-                }
-
-                mService.mDragState.mDragResult = consumed;
-                mService.mDragState.endDragLw();
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-
-    @Override
-    public void cancelDragAndDrop(IBinder dragToken) {
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "cancelDragAndDrop");
-        }
-
-        synchronized (mService.mWindowMap) {
-            long ident = Binder.clearCallingIdentity();
-            try {
-                if (mService.mDragState == null) {
-                    Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
-                    throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
-                }
-
-                if (mService.mDragState.mToken != dragToken) {
-                    Slog.w(TAG_WM,
-                            "cancelDragAndDrop() does not match prepareDrag()");
-                    throw new IllegalStateException(
-                            "cancelDragAndDrop() does not match prepareDrag()");
-                }
-
-                mService.mDragState.mDragResult = false;
-                mService.mDragState.cancelDragLw();
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-
-    @Override
-    public void dragRecipientEntered(IWindow window) {
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
-        }
-    }
-
-    @Override
-    public void dragRecipientExited(IWindow window) {
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
-        }
-    }
-
-    @Override
     public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
         synchronized(mService.mWindowMap) {
             long ident = Binder.clearCallingIdentity();
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index 1fda832..aff1bc6 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -152,7 +152,8 @@
         }
     }
 
-    public void positionChildAtBottom(TaskWindowContainerController child) {
+    public void positionChildAtBottom(TaskWindowContainerController child,
+            boolean includingParents) {
         if (child == null) {
             // TODO: Fix the call-points that cause this to happen.
             return;
@@ -164,7 +165,7 @@
                 Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
                 return;
             }
-            mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */);
+            mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents);
 
             if (mService.mAppTransition.isTransitionSet()) {
                 childTask.setSendingToBottom(true);
diff --git a/com/android/server/wm/StrictModeFlash.java b/com/android/server/wm/StrictModeFlash.java
index d1547eb..eb8ee69 100644
--- a/com/android/server/wm/StrictModeFlash.java
+++ b/com/android/server/wm/StrictModeFlash.java
@@ -44,8 +44,11 @@
     public StrictModeFlash(Display display, SurfaceSession session) {
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl(session, "StrictModeFlash",
-                1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl = new SurfaceControl.Builder(session)
+                    .setName("StrictModeFlash")
+                    .setSize(1, 1)
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
             ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101);  // one more than Watermark? arbitrary.
             ctrl.setPosition(0, 0);
diff --git a/com/android/server/wm/SurfaceControlWithBackground.java b/com/android/server/wm/SurfaceControlWithBackground.java
index b0eaf14..a5080d5 100644
--- a/com/android/server/wm/SurfaceControlWithBackground.java
+++ b/com/android/server/wm/SurfaceControlWithBackground.java
@@ -66,10 +66,10 @@
         mWindowSurfaceController = other.mWindowSurfaceController;
     }
 
-    public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format,
-            int flags, int windowType, int ownerUid,
+    public SurfaceControlWithBackground(String name, SurfaceControl.Builder b,
+            int windowType, int w, int h,
             WindowSurfaceController windowSurfaceController) throws OutOfResourcesException {
-        super(s, name, w, h, format, flags, windowType, ownerUid);
+        super(b.build());
 
         // We should only show background behind app windows that are letterboxed in a task.
         if ((windowType != TYPE_BASE_APPLICATION && windowType != TYPE_APPLICATION_STARTING)
@@ -80,9 +80,11 @@
         mLastWidth = w;
         mLastHeight = h;
         mWindowSurfaceController.getContainerRect(mTmpContainerRect);
-        mBackgroundControl = new SurfaceControl(s, "Background for - " + name,
-                mTmpContainerRect.width(), mTmpContainerRect.height(), PixelFormat.OPAQUE,
-                flags | SurfaceControl.FX_SURFACE_DIM);
+        mBackgroundControl = b.setName("Background for - " + name)
+                .setSize(mTmpContainerRect.width(), mTmpContainerRect.height())
+                .setFormat(OPAQUE)
+                .setColorLayer(true)
+                .build();
     }
 
     @Override
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 891d637..7620cb0 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -725,13 +725,13 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         proto.write(ID, mTaskId);
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final AppWindowToken appWindowToken = mChildren.get(i);
-            appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS);
+            appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS, trim);
         }
         proto.write(FILLS_PARENT, mFillsParent);
         mBounds.writeToProto(proto, BOUNDS);
diff --git a/com/android/server/wm/TaskSnapshotSurface.java b/com/android/server/wm/TaskSnapshotSurface.java
index 4698d72..3ce090a 100644
--- a/com/android/server/wm/TaskSnapshotSurface.java
+++ b/com/android/server/wm/TaskSnapshotSurface.java
@@ -304,9 +304,11 @@
         final SurfaceSession session = new SurfaceSession(mSurface);
 
         // Keep a reference to it such that it doesn't get destroyed when finalized.
-        mChildSurfaceControl = new SurfaceControl(session,
-                mTitle + " - task-snapshot-surface",
-                buffer.getWidth(), buffer.getHeight(), buffer.getFormat(), HIDDEN);
+        mChildSurfaceControl = new SurfaceControl.Builder(session)
+                .setName(mTitle + " - task-snapshot-surface")
+                .setSize(buffer.getWidth(), buffer.getHeight())
+                .setFormat(buffer.getFormat())
+                .build();
         Surface surface = new Surface();
         surface.copyFrom(mChildSurfaceControl);
 
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index d170b6f..791accf 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -1008,10 +1008,13 @@
     void resetAdjustedForIme(boolean adjustBoundsNow) {
         if (adjustBoundsNow) {
             mImeWin = null;
-            mAdjustedForIme = false;
             mImeGoingAway = false;
             mAdjustImeAmount = 0f;
             mAdjustDividerAmount = 0f;
+            if (!mAdjustedForIme) {
+                return;
+            }
+            mAdjustedForIme = false;
             updateAdjustedBounds();
             mService.setResizeDimLayer(false, getWindowingMode(), 1.0f);
         } else {
@@ -1229,12 +1232,12 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         proto.write(ID, mStackId);
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
-            mChildren.get(taskNdx).writeToProto(proto, TASKS);
+            mChildren.get(taskNdx).writeToProto(proto, TASKS, trim);
         }
         proto.write(FILLS_PARENT, mFillsParent);
         mBounds.writeToProto(proto, BOUNDS);
diff --git a/com/android/server/wm/Watermark.java b/com/android/server/wm/Watermark.java
index 171e575..d97aaac 100644
--- a/com/android/server/wm/Watermark.java
+++ b/com/android/server/wm/Watermark.java
@@ -114,8 +114,11 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl(session, "WatermarkSurface",
-                    1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl = new SurfaceControl.Builder(session)
+                    .setName("WatermarkSurface")
+                    .setSize(1, 1)
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
             ctrl.setLayerStack(mDisplay.getLayerStack());
             ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100);
             ctrl.setPosition(0, 0);
diff --git a/com/android/server/wm/WindowAnimator.java b/com/android/server/wm/WindowAnimator.java
index c01ee31..e409a68 100644
--- a/com/android/server/wm/WindowAnimator.java
+++ b/com/android/server/wm/WindowAnimator.java
@@ -223,10 +223,6 @@
                     }
                 }
 
-                if (mService.mDragState != null) {
-                    mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime);
-                }
-
                 if (!mAnimating) {
                     cancelAnimation();
                 }
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
index 563eb9c..8f4b897 100644
--- a/com/android/server/wm/WindowContainer.java
+++ b/com/android/server/wm/WindowContainer.java
@@ -677,17 +677,18 @@
      * Write to a protocol buffer output stream. Protocol buffer message definition is at
      * {@link com.android.server.wm.proto.WindowContainerProto}.
      *
-     * @param protoOutputStream Stream to write the WindowContainer object to.
-     * @param fieldId           Field Id of the WindowContainer as defined in the parent message.
+     * @param proto     Stream to write the WindowContainer object to.
+     * @param fieldId   Field Id of the WindowContainer as defined in the parent message.
+     * @param trim      If true, reduce the amount of data written.
      * @hide
      */
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
-        final long token = protoOutputStream.start(fieldId);
-        super.writeToProto(protoOutputStream, CONFIGURATION_CONTAINER);
-        protoOutputStream.write(ORIENTATION, mOrientation);
-        protoOutputStream.end(token);
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, trim);
+        proto.write(ORIENTATION, mOrientation);
+        proto.end(token);
     }
 
     private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) {
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index f0da474..f31ea67 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -357,8 +357,6 @@
     // trying to apply a new one.
     private static final boolean ALWAYS_KEEP_CURRENT = true;
 
-    private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
-
     // Enums for animation scale update types.
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE})
@@ -394,13 +392,14 @@
 
     private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
         @Override
-        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, new String[] {"-a"});
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                boolean asProto) {
+            doDump(fd, pw, new String[] {"-a"}, asProto);
         }
 
         @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, args);
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            doDump(fd, pw, args, asProto);
         }
     };
 
@@ -752,7 +751,7 @@
     boolean mAllowTheaterModeWakeFromLayout;
 
     TaskPositioner mTaskPositioner;
-    DragState mDragState = null;
+    final DragDropController mDragDropController = new DragDropController();
 
     // For frozen screen animations.
     private int mExitAnimId, mEnterAnimId;
@@ -789,7 +788,7 @@
         public void onInputEvent(InputEvent event, int displayId) {
             boolean handled = false;
             try {
-                if (mDragState == null) {
+                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.
@@ -828,12 +827,12 @@
                                     + newX + "," + newY);
                             mMuteInput = true;
                             synchronized (mWindowMap) {
-                                endDrag = mDragState.notifyDropLw(newX, newY);
+                                endDrag = mDragDropController.mDragState.notifyDropLw(newX, newY);
                             }
                         } else {
                             synchronized (mWindowMap) {
                                 // move the surface and tell the involved window(s) where we are
-                                mDragState.notifyMoveLw(newX, newY);
+                                mDragDropController.mDragState.notifyMoveLw(newX, newY);
                             }
                         }
                     } break;
@@ -843,7 +842,7 @@
                                 + newX + "," + newY);
                         mMuteInput = true;
                         synchronized (mWindowMap) {
-                            endDrag = mDragState.notifyDropLw(newX, newY);
+                            endDrag = mDragDropController.mDragState.notifyDropLw(newX, newY);
                         }
                     } break;
 
@@ -860,7 +859,7 @@
                         synchronized (mWindowMap) {
                             // endDragLw will post back to looper to dispose the receiver
                             // since we still need the receiver for the last finishInputEvent.
-                            mDragState.endDragLw();
+                            mDragDropController.mDragState.endDragLw();
                         }
                         mStylusButtonDownAtStart = false;
                         mIsStartEvent = true;
@@ -4637,73 +4636,6 @@
     }
 
     // -------------------------------------------------------------
-    // Drag and drop
-    // -------------------------------------------------------------
-
-    IBinder prepareDragSurface(IWindow window, SurfaceSession session,
-            int flags, int width, int height, Surface outSurface) {
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
-                    + " flags=" + Integer.toHexString(flags) + " win=" + window
-                    + " asbinder=" + window.asBinder());
-        }
-
-        final int callerPid = Binder.getCallingPid();
-        final int callerUid = Binder.getCallingUid();
-        final long origId = Binder.clearCallingIdentity();
-        IBinder token = null;
-
-        try {
-            synchronized (mWindowMap) {
-                try {
-                    if (mDragState == null) {
-                        // TODO(multi-display): support other displays
-                        final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                        final Display display = displayContent.getDisplay();
-
-                        SurfaceControl surface = new SurfaceControl(session, "drag surface",
-                                width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
-                        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(this, 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
-                        mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder);
-                        Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder);
-                        mH.sendMessageDelayed(msg, 5000);
-                    } else {
-                        Slog.w(TAG_WM, "Drag already in progress");
-                    }
-                } catch (OutOfResourcesException e) {
-                    Slog.e(TAG_WM, "Can't allocate drag surface w=" + width + " h=" + height, e);
-                    if (mDragState != null) {
-                        mDragState.reset();
-                        mDragState = null;
-                    }
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-
-        return token;
-    }
-
-    // -------------------------------------------------------------
     // Input Events and Focus Management
     // -------------------------------------------------------------
 
@@ -4866,6 +4798,7 @@
         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;
         public static final int WAITING_FOR_DRAWN_TIMEOUT = 24;
@@ -5120,47 +5053,12 @@
                     break;
                 }
 
-                case DRAG_START_TIMEOUT: {
-                    IBinder win = (IBinder)msg.obj;
-                    if (DEBUG_DRAG) {
-                        Slog.w(TAG_WM, "Timeout starting drag by win " + win);
-                    }
-                    synchronized (mWindowMap) {
-                        // !!! TODO: ANR the app that has failed to start the drag in time
-                        if (mDragState != null) {
-                            mDragState.unregister();
-                            mDragState.reset();
-                            mDragState = null;
-                        }
-                    }
-                    break;
-                }
-
-                case DRAG_END_TIMEOUT: {
-                    IBinder win = (IBinder)msg.obj;
-                    if (DEBUG_DRAG) {
-                        Slog.w(TAG_WM, "Timeout ending drag to win " + win);
-                    }
-                    synchronized (mWindowMap) {
-                        // !!! TODO: ANR the drag-receiving app
-                        if (mDragState != null) {
-                            mDragState.mDragResult = false;
-                            mDragState.endDragLw();
-                        }
-                    }
-                    break;
-                }
-
+                case DRAG_START_TIMEOUT:
+                case DRAG_END_TIMEOUT:
                 case 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 (mWindowMap) {
-                            interceptor.tearDown();
-                        }
-                    }
+                    mDragDropController.handleMessage(WindowManagerService.this, msg);
+                    break;
                 }
-                break;
 
                 case REPORT_HARD_KEYBOARD_STATUS_CHANGE: {
                     notifyHardKeyboardStatusChange();
@@ -6532,9 +6430,16 @@
         }
     }
 
-    private void writeToProtoLocked(ProtoOutputStream proto) {
+    /**
+     * Write to a protocol buffer output stream. Protocol buffer message definition is at
+     * {@link com.android.server.wm.proto.WindowManagerServiceProto}.
+     *
+     * @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) {
         mPolicy.writeToProto(proto, POLICY);
-        mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER);
+        mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim);
         if (mCurrentFocus != null) {
             mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
         }
@@ -6825,10 +6730,9 @@
         PriorityDump.dump(mPriorityDumper, fd, pw, args);
     }
 
-    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
         boolean dumpAll = false;
-        boolean useProto = false;
 
         int opti = 0;
         while (opti < args.length) {
@@ -6839,8 +6743,6 @@
             opti++;
             if ("-a".equals(opt)) {
                 dumpAll = true;
-            } else if ("--proto".equals(opt)) {
-                useProto = true;
             } else if ("-h".equals(opt)) {
                 pw.println("Window manager dump options:");
                 pw.println("  [-a] [-h] [cmd] ...");
@@ -6870,7 +6772,7 @@
         if (useProto) {
             final ProtoOutputStream proto = new ProtoOutputStream(fd);
             synchronized (mWindowMap) {
-                writeToProtoLocked(proto);
+                writeToProtoLocked(proto, false /* trim */);
             }
             proto.flush();
             return;
@@ -7268,7 +7170,7 @@
         }
 
         synchronized (mWindowMap) {
-            if (mDragState != null) {
+            if (mDragDropController.mDragState != null) {
                 // Drag cursor overrides the app cursor.
                 return;
             }
@@ -7702,7 +7604,8 @@
     }
 
     boolean hasWideColorGamutSupport() {
-        return mHasWideColorGamutSupport;
+        return mHasWideColorGamutSupport &&
+                !SystemProperties.getBoolean("persist.sys.sf.native_mode", false);
     }
 
     void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) {
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index e171528..4370a76 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -3120,9 +3120,9 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         writeIdentifierToProto(proto, IDENTIFIER);
         proto.write(DISPLAY_ID, getDisplayId());
         proto.write(STACK_ID, getStackId());
@@ -3137,7 +3137,7 @@
         mWinAnimator.writeToProto(proto, ANIMATOR);
         proto.write(ANIMATING_EXIT, mAnimatingExit);
         for (int i = 0; i < mChildren.size(); i++) {
-            mChildren.get(i).writeToProto(proto, CHILD_WINDOWS);
+            mChildren.get(i).writeToProto(proto, CHILD_WINDOWS, trim);
         }
         proto.end(token);
     }
diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java
index d56df55..edd650a 100644
--- a/com/android/server/wm/WindowSurfaceController.java
+++ b/com/android/server/wm/WindowSurfaceController.java
@@ -101,8 +101,14 @@
         mWindowSession = win.mSession;
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+        final SurfaceControl.Builder b = new SurfaceControl.Builder(s)
+                .setName(name)
+                .setSize(w, h)
+                .setFormat(format)
+                .setFlags(flags)
+                .setMetadata(windowType, ownerUid);
         mSurfaceControl = new SurfaceControlWithBackground(
-                s, name, w, h, format, flags, windowType, ownerUid, this);
+                name, b, windowType, w, h, this);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
         if (mService.mRoot.mSurfaceTraceEnabled) {
@@ -246,7 +252,7 @@
                 if (mAnimator.mWin.usesRelativeZOrdering()) {
                     mSurfaceControl.setRelativeLayer(
                             mAnimator.mWin.getParentWindow()
-                            .mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
+                            .mWinAnimator.mSurfaceController.mSurfaceControl,
                             -1);
                 } else {
                     mSurfaceLayer = layer;
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index fa33fe8..d57fdd2 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -1,3 +1,18 @@
+/*
+ * 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;
 
@@ -689,11 +704,14 @@
 
             // Create a new surface for the thumbnail
             WindowState window = appToken.findMainWindow();
-            SurfaceControl surfaceControl = new SurfaceControl(mService.mFxSession,
-                    "thumbnail anim", dirty.width(), dirty.height(),
-                    PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN,
-                    appToken.windowType,
-                    window != null ? window.mOwnerUid : Binder.getCallingUid());
+            final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+                    .setName("thumbnail anim")
+                    .setSize(dirty.width(), dirty.height())
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .setMetadata(appToken.windowType,
+                            window != null ? window.mOwnerUid : Binder.getCallingUid())
+                    .build();
+
             surfaceControl.setLayerStack(display.getLayerStack());
             if (SHOW_TRANSACTIONS) {
                 Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
diff --git a/com/android/server/wm/WindowToken.java b/com/android/server/wm/WindowToken.java
index 943448e..62a2abb 100644
--- a/com/android/server/wm/WindowToken.java
+++ b/com/android/server/wm/WindowToken.java
@@ -267,13 +267,13 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         proto.write(HASH_CODE, System.identityHashCode(this));
         for (int i = 0; i < mChildren.size(); i++) {
             final WindowState w = mChildren.get(i);
-            w.writeToProto(proto, WINDOWS);
+            w.writeToProto(proto, WINDOWS, trim);
         }
         proto.end(token);
     }
diff --git a/com/android/settingslib/bluetooth/A2dpProfile.java b/com/android/settingslib/bluetooth/A2dpProfile.java
index 0946181..764c592 100644
--- a/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
+import com.android.settingslib.wrapper.BluetoothA2dpWrapper;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -42,7 +43,6 @@
     private Context mContext;
 
     private BluetoothA2dp mService;
-    BluetoothA2dpWrapper.Factory mWrapperFactory;
     private BluetoothA2dpWrapper mServiceWrapper;
     private boolean mIsProfileReady;
 
@@ -67,7 +67,7 @@
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             if (V) Log.d(TAG,"Bluetooth service connected");
             mService = (BluetoothA2dp) proxy;
-            mServiceWrapper = mWrapperFactory.getInstance(mService);
+            mServiceWrapper = new BluetoothA2dpWrapper(mService);
             // We just bound to the service, so refresh the UI for any connected A2DP devices.
             List<BluetoothDevice> deviceList = mService.getConnectedDevices();
             while (!deviceList.isEmpty()) {
@@ -101,14 +101,13 @@
         mLocalAdapter = adapter;
         mDeviceManager = deviceManager;
         mProfileManager = profileManager;
-        mWrapperFactory = new BluetoothA2dpWrapperImpl.Factory();
         mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
                 BluetoothProfile.A2DP);
     }
 
     @VisibleForTesting
-    void setWrapperFactory(BluetoothA2dpWrapper.Factory factory) {
-        mWrapperFactory = factory;
+    void setBluetoothA2dpWrapper(BluetoothA2dpWrapper wrapper) {
+        mServiceWrapper = wrapper;
     }
 
     public boolean isConnectable() {
diff --git a/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java b/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
deleted file mode 100644
index aa3e835..0000000
--- a/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
+++ /dev/null
@@ -1,58 +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.settingslib.bluetooth;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothCodecStatus;
-import android.bluetooth.BluetoothDevice;
-
-/**
- * This interface replicates some methods of android.bluetooth.BluetoothA2dp that are new and not
- * yet available in our current version of  Robolectric. It provides a thin wrapper to call the real
- * methods in production and a mock in tests.
- */
-public interface BluetoothA2dpWrapper {
-
-    static interface Factory {
-        BluetoothA2dpWrapper getInstance(BluetoothA2dp service);
-    }
-
-    /**
-     * @return the real {@code BluetoothA2dp} object
-     */
-    BluetoothA2dp getService();
-
-    /**
-     * Wraps {@code BluetoothA2dp.getCodecStatus}
-     */
-    public BluetoothCodecStatus getCodecStatus();
-
-    /**
-     * Wraps {@code BluetoothA2dp.supportsOptionalCodecs}
-     */
-    int supportsOptionalCodecs(BluetoothDevice device);
-
-    /**
-     * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled}
-     */
-    int getOptionalCodecsEnabled(BluetoothDevice device);
-
-    /**
-     * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled}
-     */
-    void setOptionalCodecsEnabled(BluetoothDevice device, int value);
-}
diff --git a/com/android/settingslib/drawer/TileUtils.java b/com/android/settingslib/drawer/TileUtils.java
index 35ba6ae..038dcf8 100644
--- a/com/android/settingslib/drawer/TileUtils.java
+++ b/com/android/settingslib/drawer/TileUtils.java
@@ -65,7 +65,7 @@
      *
      * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
      */
-    private static final String EXTRA_SETTINGS_ACTION =
+    public static final String EXTRA_SETTINGS_ACTION =
             "com.android.settings.action.EXTRA_SETTINGS";
 
     /**
diff --git a/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 817989a..f4c9bb3 100644
--- a/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -415,8 +415,8 @@
                             : (mLevel == 100 ? 0.38f : 0.5f)));
             mTextHeight = -mTextPaint.getFontMetrics().ascent;
             pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level / 10) : level);
-            pctX = mWidth * 0.5f;
-            pctY = (mHeight + mTextHeight) * 0.47f;
+            pctX = mWidth * 0.5f + left;
+            pctY = (mHeight + mTextHeight) * 0.47f + top;
             pctOpaque = levelTop > pctY;
             if (!pctOpaque) {
                 mTextPath.reset();
@@ -439,8 +439,8 @@
         if (!mCharging && !mPowerSaveEnabled) {
             if (level <= mCriticalLevel) {
                 // draw the warning text
-                final float x = mWidth * 0.5f;
-                final float y = (mHeight + mWarningTextHeight) * 0.48f;
+                final float x = mWidth * 0.5f + left;
+                final float y = (mHeight + mWarningTextHeight) * 0.48f + top;
                 c.drawText(mWarningString, x, y, mWarningTextPaint);
             } else if (pctOpaque) {
                 // draw the percentage text
diff --git a/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java b/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
similarity index 64%
rename from com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
rename to com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
index 14fa796..4c52a9f 100644
--- a/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
+++ b/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
@@ -14,48 +14,56 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.bluetooth;
+package com.android.settingslib.wrapper;
 
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 
-public class BluetoothA2dpWrapperImpl implements BluetoothA2dpWrapper {
-
-    public static class Factory implements BluetoothA2dpWrapper.Factory {
-        @Override
-        public BluetoothA2dpWrapper getInstance(BluetoothA2dp service) {
-            return new BluetoothA2dpWrapperImpl(service);
-        }
-    }
+/**
+ * This class replicates some methods of android.bluetooth.BluetoothA2dp that are new and not
+ * yet available in our current version of Robolectric. It provides a thin wrapper to call the real
+ * methods in production and a mock in tests.
+ */
+public class BluetoothA2dpWrapper {
 
     private BluetoothA2dp mService;
 
-    public BluetoothA2dpWrapperImpl(BluetoothA2dp service) {
+    public BluetoothA2dpWrapper(BluetoothA2dp service) {
         mService = service;
     }
 
-    @Override
+    /**
+     * @return the real {@code BluetoothA2dp} object
+     */
     public BluetoothA2dp getService() {
         return mService;
     }
 
-    @Override
+    /**
+     * Wraps {@code BluetoothA2dp.getCodecStatus}
+     */
     public BluetoothCodecStatus getCodecStatus() {
         return mService.getCodecStatus();
     }
 
-    @Override
+    /**
+     * Wraps {@code BluetoothA2dp.supportsOptionalCodecs}
+     */
     public int supportsOptionalCodecs(BluetoothDevice device) {
         return mService.supportsOptionalCodecs(device);
     }
 
-    @Override
+    /**
+     * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled}
+     */
     public int getOptionalCodecsEnabled(BluetoothDevice device) {
         return mService.getOptionalCodecsEnabled(device);
     }
 
-    @Override
+    /**
+     * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled}
+     */
     public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
         mService.setOptionalCodecsEnabled(device, value);
     }
diff --git a/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
index 3d11e12..154339a 100644
--- a/com/android/setupwizardlib/test/util/DrawingTestActivity.java
+++ b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
@@ -16,14 +16,15 @@
 
 package com.android.setupwizardlib.test.util;
 
-import android.app.Activity;
+import android.support.v7.app.AppCompatActivity;
 
 /**
  * Activity to test view and drawable drawing behaviors. This is used to make sure that the drawing
- * behavior tested is the same as it would when inflated as part of an activity, including any
- * injected layout inflater factories and custom themes etc.
+ * behavior tested is the same as it would when inflated as part of an {@link AppCompatActivity},
+ * including custom layout inflaters and theme values that the support library injects to the
+ * activity.
  *
  * @see DrawingTestHelper
  */
-public class DrawingTestActivity extends Activity {
+public class DrawingTestActivity extends AppCompatActivity {
 }
diff --git a/com/android/setupwizardlib/view/NavigationBarButton.java b/com/android/setupwizardlib/view/NavigationBarButton.java
index 45d3737..5172c47 100644
--- a/com/android/setupwizardlib/view/NavigationBarButton.java
+++ b/com/android/setupwizardlib/view/NavigationBarButton.java
@@ -17,16 +17,143 @@
 package com.android.setupwizardlib.view;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
 import android.util.AttributeSet;
 import android.widget.Button;
 
+/**
+ * Button for navigation bar, which includes tinting of its compound drawables to be used for dark
+ * and light themes.
+ */
 public class NavigationBarButton extends Button {
 
     public NavigationBarButton(Context context) {
         super(context);
+        init();
     }
 
     public NavigationBarButton(Context context, AttributeSet attrs) {
         super(context, attrs);
+        init();
+    }
+
+    private void init() {
+        // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter,
+        // so manually getting it and wrapping it in the compat drawable.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            Drawable[] drawables = getCompoundDrawablesRelative();
+            for (int i = 0; i < drawables.length; i++) {
+                if (drawables[i] != null) {
+                    drawables[i] = TintedDrawable.wrap(drawables[i]);
+                }
+            }
+            setCompoundDrawablesRelativeWithIntrinsicBounds(drawables[0], drawables[1],
+                    drawables[2], drawables[3]);
+        }
+    }
+
+    @Override
+    public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
+        if (left != null) left = TintedDrawable.wrap(left);
+        if (top != null) top = TintedDrawable.wrap(top);
+        if (right != null) right = TintedDrawable.wrap(right);
+        if (bottom != null) bottom = TintedDrawable.wrap(bottom);
+        super.setCompoundDrawables(left, top, right, bottom);
+        tintDrawables();
+    }
+
+    @Override
+    public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end,
+            Drawable bottom) {
+        if (start != null) start = TintedDrawable.wrap(start);
+        if (top != null) top = TintedDrawable.wrap(top);
+        if (end != null) end = TintedDrawable.wrap(end);
+        if (bottom != null) bottom = TintedDrawable.wrap(bottom);
+        super.setCompoundDrawablesRelative(start, top, end, bottom);
+        tintDrawables();
+    }
+
+    @Override
+    public void setTextColor(ColorStateList colors) {
+        super.setTextColor(colors);
+        tintDrawables();
+    }
+
+    private void tintDrawables() {
+        final ColorStateList textColors = getTextColors();
+        if (textColors != null) {
+            for (Drawable drawable : getAllCompoundDrawables()) {
+                if (drawable instanceof TintedDrawable) {
+                    ((TintedDrawable) drawable).setTintListCompat(textColors);
+                }
+            }
+            invalidate();
+        }
+    }
+
+    private Drawable[] getAllCompoundDrawables() {
+        Drawable[] drawables = new Drawable[6];
+        Drawable[] compoundDrawables = getCompoundDrawables();
+        drawables[0] = compoundDrawables[0];  // left
+        drawables[1] = compoundDrawables[1];  // top
+        drawables[2] = compoundDrawables[2];  // right
+        drawables[3] = compoundDrawables[3];  // bottom
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative();
+            drawables[4] = compoundDrawablesRelative[0];  // start
+            drawables[5] = compoundDrawablesRelative[2];  // end
+        }
+        return drawables;
+    }
+
+    // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0
+    // or above
+    private static class TintedDrawable extends LayerDrawable {
+
+        public static TintedDrawable wrap(Drawable drawable) {
+            if (drawable instanceof TintedDrawable) {
+                return (TintedDrawable) drawable;
+            }
+            return new TintedDrawable(drawable.mutate());
+        }
+
+        private ColorStateList mTintList = null;
+
+        TintedDrawable(Drawable wrapped) {
+            super(new Drawable[] { wrapped });
+        }
+
+        @Override
+        public boolean isStateful() {
+            return true;
+        }
+
+        @Override
+        public boolean setState(@NonNull int[] stateSet) {
+            boolean needsInvalidate = super.setState(stateSet);
+            boolean needsInvalidateForState = updateState();
+            return needsInvalidate || needsInvalidateForState;
+        }
+
+        public void setTintListCompat(ColorStateList colors) {
+            mTintList = colors;
+            if (updateState()) {
+                invalidateSelf();
+            }
+        }
+
+        private boolean updateState() {
+            if (mTintList != null) {
+                final int color = mTintList.getColorForState(getState(), 0);
+                setColorFilter(color, PorterDuff.Mode.SRC_IN);
+                return true;  // Needs invalidate
+            }
+            return false;
+        }
     }
 }
diff --git a/com/android/setupwizardlib/view/RichTextView.java b/com/android/setupwizardlib/view/RichTextView.java
index 5a78561..e6bc9da 100644
--- a/com/android/setupwizardlib/view/RichTextView.java
+++ b/com/android/setupwizardlib/view/RichTextView.java
@@ -17,6 +17,11 @@
 package com.android.setupwizardlib.view;
 
 import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.AppCompatTextView;
 import android.text.Annotation;
 import android.text.SpannableString;
 import android.text.Spanned;
@@ -25,22 +30,18 @@
 import android.text.style.TextAppearanceSpan;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.widget.TextView;
+import android.view.MotionEvent;
 
 import com.android.setupwizardlib.span.LinkSpan;
 import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener;
 import com.android.setupwizardlib.span.SpanHelper;
+import com.android.setupwizardlib.util.LinkAccessibilityHelper;
 
 /**
  * An extension of TextView that automatically replaces the annotation tags as specified in
  * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)}
- *
- * <p>Note: The accessibility interaction for ClickableSpans (and therefore LinkSpans) are built
- * into platform in O, although the interaction paradigm is different. (See b/17726921). In this
- * platform version, the links are exposed in the Local Context Menu of TalkBack instead of
- * accessible directly through swiping.
  */
-public class RichTextView extends TextView implements OnLinkClickListener {
+public class RichTextView extends AppCompatTextView implements OnLinkClickListener {
 
     /* static section */
 
@@ -88,14 +89,22 @@
 
     /* non-static section */
 
+    private LinkAccessibilityHelper mAccessibilityHelper;
     private OnLinkClickListener mOnLinkClickListener;
 
     public RichTextView(Context context) {
         super(context);
+        init();
     }
 
     public RichTextView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        init();
+    }
+
+    private void init() {
+        mAccessibilityHelper = new LinkAccessibilityHelper(this);
+        ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
     }
 
     @Override
@@ -132,6 +141,32 @@
         return false;
     }
 
+    @Override
+    protected boolean dispatchHoverEvent(MotionEvent event) {
+        if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) {
+            return true;
+        }
+        return super.dispatchHoverEvent(event);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
+            // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a
+            // workaround, set the state on those drawables directly.
+            final int[] state = getDrawableState();
+            for (Drawable drawable : getCompoundDrawablesRelative()) {
+                if (drawable != null) {
+                    if (drawable.setState(state)) {
+                        invalidateDrawable(drawable);
+                    }
+                }
+            }
+        }
+    }
+
     public void setOnLinkClickListener(OnLinkClickListener listener) {
         mOnLinkClickListener = listener;
     }
diff --git a/com/android/systemui/ForegroundServiceControllerImpl.java b/com/android/systemui/ForegroundServiceControllerImpl.java
index c930d56..3714c4e 100644
--- a/com/android/systemui/ForegroundServiceControllerImpl.java
+++ b/com/android/systemui/ForegroundServiceControllerImpl.java
@@ -34,6 +34,10 @@
  */
 public class ForegroundServiceControllerImpl
         implements ForegroundServiceController {
+  
+    // shelf life of foreground services before they go bad
+    public static final long FG_SERVICE_GRACE_MILLIS = 5000;
+
     private static final String TAG = "FgServiceController";
     private static final boolean DBG = false;
 
@@ -72,7 +76,7 @@
             if (isDungeonNotification(sbn)) {
                 // if you remove the dungeon entirely, we take that to mean there are
                 // no running services
-                userServices.setRunningServices(null);
+                userServices.setRunningServices(null, 0);
                 return true;
             } else {
                 // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE
@@ -94,7 +98,7 @@
                 final Bundle extras = sbn.getNotification().extras;
                 if (extras != null) {
                     final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
-                    userServices.setRunningServices(svcs); // null ok
+                    userServices.setRunningServices(svcs, sbn.getNotification().when);
                 }
             } else {
                 userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
@@ -118,9 +122,11 @@
      */
     private static class UserServices {
         private String[] mRunning = null;
+        private long mServiceStartTime = 0;
         private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1);
-        public void setRunningServices(String[] pkgs) {
+        public void setRunningServices(String[] pkgs, long serviceStartTime) {
             mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
+            mServiceStartTime = serviceStartTime;
         }
         public void addNotification(String pkg, String key) {
             if (mNotifications.get(pkg) == null) {
@@ -142,7 +148,9 @@
             return found;
         }
         public boolean isDungeonNeeded() {
-            if (mRunning != null) {
+            if (mRunning != null
+                && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) {
+
                 for (String pkg : mRunning) {
                     final ArraySet<String> set = mNotifications.get(pkg);
                     if (set == null || set.size() == 0) {
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index 312b990..c92562b 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -599,8 +599,8 @@
     private boolean isSettingsShown() {
         List<RunningTaskInfo> runningTasks;
         try {
-            runningTasks = mActivityManager.getTasks(1, 0);
-            if (runningTasks == null || runningTasks.size() == 0) {
+            runningTasks = mActivityManager.getTasks(1);
+            if (runningTasks.isEmpty()) {
                 return false;
             }
         } catch (RemoteException e) {
diff --git a/com/android/systemui/qs/QSDetailItems.java b/com/android/systemui/qs/QSDetailItems.java
index 8869e8d..ddd9910 100644
--- a/com/android/systemui/qs/QSDetailItems.java
+++ b/com/android/systemui/qs/QSDetailItems.java
@@ -185,10 +185,10 @@
             }
             view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
             final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
-            if (item.iconDrawable != null) {
-                iv.setImageDrawable(item.iconDrawable.getDrawable(iv.getContext()));
+            if (item.icon != null) {
+                iv.setImageDrawable(item.icon.getDrawable(iv.getContext()));
             } else {
-                iv.setImageResource(item.icon);
+                iv.setImageResource(item.iconResId);
             }
             iv.getOverlay().clear();
             if (item.overlay != null) {
@@ -258,8 +258,8 @@
     }
 
     public static class Item {
-        public int icon;
-        public QSTile.Icon iconDrawable;
+        public int iconResId;
+        public QSTile.Icon icon;
         public Drawable overlay;
         public CharSequence line1;
         public CharSequence line2;
diff --git a/com/android/systemui/qs/tiles/BluetoothTile.java b/com/android/systemui/qs/tiles/BluetoothTile.java
index bc3ccb4..1aecdce 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 BluetoothBatteryDrawable(batteryLevel,
+                        state.icon = new BluetoothBatteryTileIcon(batteryLevel,
                                 mContext.getResources().getFraction(
                                         R.fraction.bt_battery_scale_fraction, 1, 1));
                     }
@@ -212,15 +212,11 @@
         return new BluetoothDetailAdapter();
     }
 
-    private class BluetoothBatteryDrawable extends Icon {
+    private class BluetoothBatteryTileIcon extends Icon {
         private int mLevel;
         private float mIconScale;
 
-        BluetoothBatteryDrawable(int level) {
-            this(level, 1 /* iconScale */);
-        }
-
-        BluetoothBatteryDrawable(int level, float iconScale) {
+        BluetoothBatteryTileIcon(int level, float iconScale) {
             mLevel = level;
             mIconScale = iconScale;
         }
@@ -302,15 +298,16 @@
                 for (CachedBluetoothDevice device : devices) {
                     if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
                     final Item item = new Item();
-                    item.icon = R.drawable.ic_qs_bluetooth_on;
+                    item.iconResId = R.drawable.ic_qs_bluetooth_on;
                     item.line1 = device.getName();
                     item.tag = device;
                     int state = device.getMaxConnectionState();
                     if (state == BluetoothProfile.STATE_CONNECTED) {
-                        item.icon = R.drawable.ic_qs_bluetooth_connected;
+                        item.iconResId = R.drawable.ic_qs_bluetooth_connected;
                         int batteryLevel = device.getBatteryLevel();
                         if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                            item.iconDrawable = new BluetoothBatteryDrawable(batteryLevel);
+                            item.icon = new BluetoothBatteryTileIcon(batteryLevel,
+                                    1 /* iconScale */);
                             item.line2 = mContext.getString(
                                     R.string.quick_settings_connected_battery_level,
                                     Utils.formatPercentage(batteryLevel));
@@ -321,7 +318,7 @@
                         items.add(connectedDevices, item);
                         connectedDevices++;
                     } else if (state == BluetoothProfile.STATE_CONNECTING) {
-                        item.icon = R.drawable.ic_qs_bluetooth_connecting;
+                        item.iconResId = R.drawable.ic_qs_bluetooth_connecting;
                         item.line2 = mContext.getString(R.string.quick_settings_connecting);
                         items.add(connectedDevices, item);
                     } else {
diff --git a/com/android/systemui/qs/tiles/CastTile.java b/com/android/systemui/qs/tiles/CastTile.java
index fb396b9..678aa71 100644
--- a/com/android/systemui/qs/tiles/CastTile.java
+++ b/com/android/systemui/qs/tiles/CastTile.java
@@ -19,26 +19,17 @@
 import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
 
 import android.app.Dialog;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.MediaRouter;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.widget.Button;
 
-import com.android.internal.app.MediaRouteChooserDialog;
-import com.android.internal.app.MediaRouteControllerDialog;
 import com.android.internal.app.MediaRouteDialogPresenter;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -280,7 +271,7 @@
                 for (CastDevice device : devices) {
                     if (device.state == CastDevice.STATE_CONNECTED) {
                         final Item item = new Item();
-                        item.icon = R.drawable.ic_qs_cast_on;
+                        item.iconResId = R.drawable.ic_qs_cast_on;
                         item.line1 = getDeviceName(device);
                         item.line2 = mContext.getString(R.string.quick_settings_connected);
                         item.tag = device;
@@ -300,7 +291,7 @@
                         final CastDevice device = mVisibleOrder.get(id);
                         if (!devices.contains(device)) continue;
                         final Item item = new Item();
-                        item.icon = R.drawable.ic_qs_cast_off;
+                        item.iconResId = R.drawable.ic_qs_cast_off;
                         item.line1 = getDeviceName(device);
                         if (device.state == CastDevice.STATE_CONNECTING) {
                             item.line2 = mContext.getString(R.string.quick_settings_connecting);
diff --git a/com/android/systemui/qs/tiles/WifiTile.java b/com/android/systemui/qs/tiles/WifiTile.java
index 2370273..977a725 100644
--- a/com/android/systemui/qs/tiles/WifiTile.java
+++ b/com/android/systemui/qs/tiles/WifiTile.java
@@ -402,7 +402,7 @@
                     final AccessPoint ap = mAccessPoints[i];
                     final Item item = new Item();
                     item.tag = ap;
-                    item.icon = mWifiController.getIcon(ap);
+                    item.iconResId = mWifiController.getIcon(ap);
                     item.line1 = ap.getSsid();
                     item.line2 = ap.isActive() ? ap.getSummary() : null;
                     item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index 87f24fd..55ec5e7 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -200,25 +200,17 @@
      */
     public ActivityManager.RunningTaskInfo getRunningTask() {
         // Note: The set of running tasks from the system is ordered by recency
-        List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10);
-        if (tasks == null || tasks.isEmpty()) {
+        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;
         }
-
-        // Find the first task in a valid stack, we ignore everything from the Recents and PiP
-        // stacks
-        for (int i = 0; i < tasks.size(); i++) {
-            final ActivityManager.RunningTaskInfo task = tasks.get(i);
-            final WindowConfiguration winConfig = task.configuration.windowConfiguration;
-            if (winConfig.getActivityType() == ACTIVITY_TYPE_RECENTS) {
-                continue;
-            }
-            if (winConfig.getWindowingMode() == WINDOWING_MODE_PINNED) {
-                continue;
-            }
-            return task;
-        }
-        return null;
     }
 
     /**
diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 25c2fc9..7442904 100644
--- a/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -302,11 +302,6 @@
      */
     private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
             final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
-        if (activityType == ACTIVITY_TYPE_RECENTS || activityType == ACTIVITY_TYPE_HOME
-                || windowingMode == WINDOWING_MODE_PINNED) {
-            return null;
-        }
-
         // Calculate the offscreen task rect (for tasks that are not backed by views)
         TaskView taskView = stackView.getChildViewForTask(task);
         TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
diff --git a/com/android/systemui/statusbar/ActivatableNotificationView.java b/com/android/systemui/statusbar/ActivatableNotificationView.java
index 68fe9a8..84b7015 100644
--- a/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -29,7 +29,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewAnimationUtils;
-import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
@@ -173,12 +172,12 @@
     private int mOverrideTint;
     private float mOverrideAmount;
     private boolean mShadowHidden;
-    private boolean mWasActivatedOnDown;
     /**
      * Similar to mDimmed but is also true if it's not dimmable but should be
      */
     private boolean mNeedsDimming;
     private int mDimmedAlpha;
+    private boolean mBlockNextTouch;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -204,7 +203,7 @@
             } else {
                 makeInactive(true /* animate */);
             }
-        }, this::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
+        }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
     }
 
     @Override
@@ -241,9 +240,15 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (mNeedsDimming && !mActivated && ev.getActionMasked() == MotionEvent.ACTION_DOWN
+        if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN
                 && disallowSingleClick(ev) && !isTouchExplorationEnabled()) {
-            return true;
+            if (!mActivated) {
+                return true;
+            } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
+                mBlockNextTouch = true;
+                makeInactive(true /* animate */);
+                return true;
+            }
         }
         return super.onInterceptTouchEvent(ev);
     }
@@ -263,10 +268,11 @@
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         boolean result;
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            mWasActivatedOnDown = mActivated;
+        if (mBlockNextTouch) {
+            mBlockNextTouch = false;
+            return false;
         }
-        if ((mNeedsDimming && !mActivated) && !isTouchExplorationEnabled() && isInteractive()) {
+        if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) {
             boolean wasActivated = mActivated;
             result = handleTouchEventDimmed(event);
             if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
@@ -312,7 +318,7 @@
 
     @Override
     public boolean performClick() {
-        if (mWasActivatedOnDown || !mNeedsDimming || isTouchExplorationEnabled()) {
+        if (!mNeedsDimming || isTouchExplorationEnabled()) {
             return super.performClick();
         }
         return false;
diff --git a/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/com/android/systemui/statusbar/phone/DoubleTapHelper.java
index dcb6a38..0d62703 100644
--- a/com/android/systemui/statusbar/phone/DoubleTapHelper.java
+++ b/com/android/systemui/statusbar/phone/DoubleTapHelper.java
@@ -142,7 +142,7 @@
                 && Math.abs(event.getY() - mDownY) < mTouchSlop;
     }
 
-    private boolean isWithinDoubleTapSlop(MotionEvent event) {
+    public boolean isWithinDoubleTapSlop(MotionEvent event) {
         if (!mActivated) {
             // If we're not activated there's no double tap slop to satisfy.
             return true;
diff --git a/com/android/systemui/statusbar/phone/NavigationBarView.java b/com/android/systemui/statusbar/phone/NavigationBarView.java
index 9a7039a..094129c 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -448,10 +448,18 @@
 
         // Always disable recents when alternate car mode UI is active.
         boolean disableRecent = mUseCarModeUi
-                        || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
-        final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
+                || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
+
+        boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
                 && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
 
+        if ((disableRecent || disableBack) && inScreenPinning()) {
+            // Don't hide back and recents buttons when in screen pinning mode, as they are used for
+            // exiting.
+            disableBack = false;
+            disableRecent = false;
+        }
+
         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
         if (navButtons != null) {
             LayoutTransition lt = navButtons.getLayoutTransition();
@@ -461,20 +469,16 @@
                 }
             }
         }
-        if (inLockTask() && disableRecent && !disableHome) {
-            // Don't hide recents when in lock task, it is used for exiting.
-            // Unless home is hidden, then in DPM locked mode and no exit available.
-            disableRecent = false;
-        }
 
         getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
         getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
         getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
     }
 
-    private boolean inLockTask() {
+    private boolean inScreenPinning() {
         try {
-            return ActivityManager.getService().isInLockTaskMode();
+            return ActivityManager.getService().getLockTaskModeState()
+                    == ActivityManager.LOCK_TASK_MODE_PINNED;
         } catch (RemoteException e) {
             return false;
         }
diff --git a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index d24e51c..40ee838 100644
--- a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -382,13 +382,6 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... args) {
-                // Disable tethering if enabling Wifi
-                final int wifiApState = mWifiManager.getWifiApState();
-                if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
-                        (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
-                    mWifiManager.setWifiApEnabled(null, false);
-                }
-
                 mWifiManager.setWifiEnabled(enabled);
                 return null;
             }
diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
index 7c9aede..3d5476d 100644
--- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
+++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,24 +16,55 @@
 
 package com.android.uiautomator.testrunner;
 
-import android.app.Instrumentation;
+import android.content.Context;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.test.InstrumentationTestCase;
+import android.view.inputmethod.InputMethodInfo;
 
-import com.android.uiautomator.core.InstrumentationUiAutomatorBridge;
+import com.android.internal.view.IInputMethodManager;
 import com.android.uiautomator.core.UiDevice;
 
+import junit.framework.TestCase;
+
+import java.util.List;
+
 /**
- * UI Automator test case that is executed on the device.
+ * UI automation test should extend this class. This class provides access
+ * to the following:
+ * {@link UiDevice} instance
+ * {@link Bundle} for command line parameters.
+ * @since API Level 16
  * @deprecated New tests should be written using UI Automator 2.0 which is available as part of the
  * Android Testing Support Library.
  */
 @Deprecated
-public class UiAutomatorTestCase extends InstrumentationTestCase {
+public class UiAutomatorTestCase extends TestCase {
 
+    private static final String DISABLE_IME = "disable_ime";
+    private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime";
+    private UiDevice mUiDevice;
     private Bundle mParams;
     private IAutomationSupport mAutomationSupport;
+    private boolean mShouldDisableIme = false;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mShouldDisableIme = "true".equals(mParams.getString(DISABLE_IME));
+        if (mShouldDisableIme) {
+            setDummyIme();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mShouldDisableIme) {
+            restoreActiveIme();
+        }
+        super.tearDown();
+    }
 
     /**
      * Get current instance of {@link UiDevice}. Works similar to calling the static
@@ -41,7 +72,7 @@
      * @since API Level 16
      */
     public UiDevice getUiDevice() {
-        return UiDevice.getInstance();
+        return mUiDevice;
     }
 
     /**
@@ -54,43 +85,34 @@
         return mParams;
     }
 
-    void setAutomationSupport(IAutomationSupport automationSupport) {
-        mAutomationSupport = automationSupport;
-    }
-
     /**
      * Provides support for running tests to report interim status
      *
      * @return IAutomationSupport
      * @since API Level 16
-     * @deprecated Use {@link Instrumentation#sendStatus(int, Bundle)} instead
      */
     public IAutomationSupport getAutomationSupport() {
-        if (mAutomationSupport == null) {
-            mAutomationSupport = new InstrumentationAutomationSupport(getInstrumentation());
-        }
         return mAutomationSupport;
     }
 
     /**
-     * Initializes this test case.
-     *
-     * @param params Instrumentation arguments.
+     * package private
+     * @param uiDevice
      */
-    void initialize(Bundle params) {
+    void setUiDevice(UiDevice uiDevice) {
+        mUiDevice = uiDevice;
+    }
+
+    /**
+     * package private
+     * @param params
+     */
+    void setParams(Bundle params) {
         mParams = params;
+    }
 
-        // check if this is a monkey test mode
-        String monkeyVal = mParams.getString("monkey");
-        if (monkeyVal != null) {
-            // only if the monkey key is specified, we alter the state of monkey
-            // else we should leave things as they are.
-            getInstrumentation().getUiAutomation().setRunAsMonkey(Boolean.valueOf(monkeyVal));
-        }
-
-        UiDevice.getInstance().initialize(new InstrumentationUiAutomatorBridge(
-                getInstrumentation().getContext(),
-                getInstrumentation().getUiAutomation()));
+    void setAutomationSupport(IAutomationSupport automationSupport) {
+        mAutomationSupport = automationSupport;
     }
 
     /**
@@ -101,4 +123,28 @@
     public void sleep(long ms) {
         SystemClock.sleep(ms);
     }
+
+    private void setDummyIme() throws RemoteException {
+        IInputMethodManager im = IInputMethodManager.Stub.asInterface(ServiceManager
+                .getService(Context.INPUT_METHOD_SERVICE));
+        List<InputMethodInfo> infos = im.getInputMethodList();
+        String id = null;
+        for (InputMethodInfo info : infos) {
+            if (DUMMY_IME_PACKAGE.equals(info.getComponent().getPackageName())) {
+                id = info.getId();
+            }
+        }
+        if (id == null) {
+            throw new RuntimeException(String.format(
+                    "Required testing fixture missing: IME package (%s)", DUMMY_IME_PACKAGE));
+        }
+        im.setInputMethod(null, id);
+    }
+
+    private void restoreActiveIme() throws RemoteException {
+        // TODO: figure out a way to restore active IME
+        // Currently retrieving active IME requires querying secure settings provider, which is hard
+        // to do without a Context; so the caveat here is that to make the post test device usable,
+        // the active IME needs to be manually switched.
+    }
 }
diff --git a/foo/bar/ComplexDao.java b/foo/bar/ComplexDao.java
index dbb54fb..89859e5 100644
--- a/foo/bar/ComplexDao.java
+++ b/foo/bar/ComplexDao.java
@@ -1,433 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package foo.bar;
-
-import android.arch.lifecycle.ComputableLiveData;
-import android.arch.lifecycle.LiveData;
-import android.arch.persistence.room.InvalidationTracker.Observer;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.RoomSQLiteQuery;
-import android.arch.persistence.room.util.StringUtil;
-import android.database.Cursor;
-import android.support.annotation.NonNull;
-import java.lang.Integer;
-import java.lang.Override;
-import java.lang.String;
-import java.lang.StringBuilder;
-import java.util.ArrayList;
+import android.arch.persistence.room.*;
 import java.util.List;
-import java.util.Set;
-import javax.annotation.Generated;
-
-@Generated("android.arch.persistence.room.RoomProcessor")
-public class ComplexDao_Impl extends ComplexDao {
-    private final RoomDatabase __db;
-
-    public ComplexDao_Impl(ComplexDatabase __db) {
-        super(__db);
-        this.__db = __db;
+import android.arch.lifecycle.LiveData;
+@Dao
+abstract class ComplexDao {
+    static class FullName {
+        public int id;
+        public String fullName;
     }
 
-    @Override
+    private final ComplexDatabase mDb;
+
+    public ComplexDao(ComplexDatabase db) {
+        mDb = db;
+    }
+
+    @Transaction
     public boolean transactionMethod(int i, String s, long l) {
-        __db.beginTransaction();
-        try {
-            boolean _result = super.transactionMethod(i, s, l);
-            __db.setTransactionSuccessful();
-            return _result;
-        } finally {
-            __db.endTransaction();
-        }
+        return true;
     }
 
-    @Override
-    public List<ComplexDao.FullName> fullNames(int id) {
-        final String _sql = "SELECT name || lastName as fullName, uid as id FROM user where uid = ?";
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
-        int _argIndex = 1;
-        _statement.bindLong(_argIndex, id);
-        final Cursor _cursor = __db.query(_statement);
-        try {
-            final int _cursorIndexOfFullName = _cursor.getColumnIndexOrThrow("fullName");
-            final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
-            final List<ComplexDao.FullName> _result = new ArrayList<ComplexDao.FullName>(_cursor.getCount());
-            while(_cursor.moveToNext()) {
-                final ComplexDao.FullName _item;
-                _item = new ComplexDao.FullName();
-                _item.fullName = _cursor.getString(_cursorIndexOfFullName);
-                _item.id = _cursor.getInt(_cursorIndexOfId);
-                _result.add(_item);
-            }
-            return _result;
-        } finally {
-            _cursor.close();
-            _statement.release();
-        }
-    }
+    @Query("SELECT name || lastName as fullName, uid as id FROM user where uid = :id")
+    abstract public List<FullName> fullNames(int id);
 
-    @Override
-    public User getById(int id) {
-        final String _sql = "SELECT * FROM user where uid = ?";
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
-        int _argIndex = 1;
-        _statement.bindLong(_argIndex, id);
-        final Cursor _cursor = __db.query(_statement);
-        try {
-            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
-            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
-            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
-            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
-            final User _result;
-            if(_cursor.moveToFirst()) {
-                _result = new User();
-                _result.uid = _cursor.getInt(_cursorIndexOfUid);
-                _result.name = _cursor.getString(_cursorIndexOfName);
-                final String _tmpLastName;
-                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-                _result.setLastName(_tmpLastName);
-                _result.age = _cursor.getInt(_cursorIndexOfAge);
-            } else {
-                _result = null;
-            }
-            return _result;
-        } finally {
-            _cursor.close();
-            _statement.release();
-        }
-    }
+    @Query("SELECT * FROM user where uid = :id")
+    abstract public User getById(int id);
 
-    @Override
-    public User findByName(String name, String lastName) {
-        final String _sql = "SELECT * FROM user where name LIKE ? AND lastName LIKE ?";
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 2);
-        int _argIndex = 1;
-        if (name == null) {
-            _statement.bindNull(_argIndex);
-        } else {
-            _statement.bindString(_argIndex, name);
-        }
-        _argIndex = 2;
-        if (lastName == null) {
-            _statement.bindNull(_argIndex);
-        } else {
-            _statement.bindString(_argIndex, lastName);
-        }
-        final Cursor _cursor = __db.query(_statement);
-        try {
-            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
-            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
-            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
-            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
-            final User _result;
-            if(_cursor.moveToFirst()) {
-                _result = new User();
-                _result.uid = _cursor.getInt(_cursorIndexOfUid);
-                _result.name = _cursor.getString(_cursorIndexOfName);
-                final String _tmpLastName;
-                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-                _result.setLastName(_tmpLastName);
-                _result.age = _cursor.getInt(_cursorIndexOfAge);
-            } else {
-                _result = null;
-            }
-            return _result;
-        } finally {
-            _cursor.close();
-            _statement.release();
-        }
-    }
+    @Query("SELECT * FROM user where name LIKE :name AND lastName LIKE :lastName")
+    abstract public User findByName(String name, String lastName);
 
-    @Override
-    public List<User> loadAllByIds(int... ids) {
-        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
-        _stringBuilder.append("SELECT * FROM user where uid IN (");
-        final int _inputSize = ids.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        final int _argCount = 0 + _inputSize;
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
-        int _argIndex = 1;
-        for (int _item : ids) {
-            _statement.bindLong(_argIndex, _item);
-            _argIndex ++;
-        }
-        final Cursor _cursor = __db.query(_statement);
-        try {
-            final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
-            final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
-            final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
-            final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
-            final List<User> _result = new ArrayList<User>(_cursor.getCount());
-            while(_cursor.moveToNext()) {
-                final User _item_1;
-                _item_1 = new User();
-                _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
-                _item_1.name = _cursor.getString(_cursorIndexOfName);
-                final String _tmpLastName;
-                _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-                _item_1.setLastName(_tmpLastName);
-                _item_1.age = _cursor.getInt(_cursorIndexOfAge);
-                _result.add(_item_1);
-            }
-            return _result;
-        } finally {
-            _cursor.close();
-            _statement.release();
-        }
-    }
+    @Query("SELECT * FROM user where uid IN (:ids)")
+    abstract public List<User> loadAllByIds(int... ids);
 
-    @Override
-    int getAge(int id) {
-        final String _sql = "SELECT ageColumn FROM user where uid = ?";
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
-        int _argIndex = 1;
-        _statement.bindLong(_argIndex, id);
-        final Cursor _cursor = __db.query(_statement);
-        try {
-            final int _result;
-            if(_cursor.moveToFirst()) {
-                _result = _cursor.getInt(0);
-            } else {
-                _result = 0;
-            }
-            return _result;
-        } finally {
-            _cursor.close();
-            _statement.release();
-        }
-    }
+    @Query("SELECT ageColumn FROM user where uid = :id")
+    abstract int getAge(int id);
 
-    @Override
-    public int[] getAllAges(int... ids) {
-        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
-        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
-        final int _inputSize = ids.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        final int _argCount = 0 + _inputSize;
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
-        int _argIndex = 1;
-        for (int _item : ids) {
-            _statement.bindLong(_argIndex, _item);
-            _argIndex ++;
-        }
-        final Cursor _cursor = __db.query(_statement);
-        try {
-            final int[] _result = new int[_cursor.getCount()];
-            int _index = 0;
-            while(_cursor.moveToNext()) {
-                final int _item_1;
-                _item_1 = _cursor.getInt(0);
-                _result[_index] = _item_1;
-                _index ++;
-            }
-            return _result;
-        } finally {
-            _cursor.close();
-            _statement.release();
-        }
-    }
+    @Query("SELECT ageColumn FROM user where uid IN(:ids)")
+    abstract public int[] getAllAges(int... ids);
 
-    @Override
-    public List<Integer> getAllAgesAsList(List<Integer> ids) {
-        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
-        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
-        final int _inputSize = ids.size();
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        final int _argCount = 0 + _inputSize;
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
-        int _argIndex = 1;
-        for (Integer _item : ids) {
-            if (_item == null) {
-                _statement.bindNull(_argIndex);
-            } else {
-                _statement.bindLong(_argIndex, _item);
-            }
-            _argIndex ++;
-        }
-        final Cursor _cursor = __db.query(_statement);
-        try {
-            final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
-            while(_cursor.moveToNext()) {
-                final Integer _item_1;
-                if (_cursor.isNull(0)) {
-                    _item_1 = null;
-                } else {
-                    _item_1 = _cursor.getInt(0);
-                }
-                _result.add(_item_1);
-            }
-            return _result;
-        } finally {
-            _cursor.close();
-            _statement.release();
-        }
-    }
+    @Query("SELECT ageColumn FROM user where uid IN(:ids)")
+    abstract public List<Integer> getAllAgesAsList(List<Integer> ids);
 
-    @Override
-    public LiveData<User> getByIdLive(int id) {
-        final String _sql = "SELECT * FROM user where uid = ?";
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
-        int _argIndex = 1;
-        _statement.bindLong(_argIndex, id);
-        return new ComputableLiveData<User>() {
-            private Observer _observer;
+    @Query("SELECT * FROM user where uid = :id")
+    abstract public LiveData<User> getByIdLive(int id);
 
-            @Override
-            protected User compute() {
-                if (_observer == null) {
-                    _observer = new Observer("user") {
-                        @Override
-                        public void onInvalidated(@NonNull Set<String> tables) {
-                            invalidate();
-                        }
-                    };
-                    __db.getInvalidationTracker().addWeakObserver(_observer);
-                }
-                final Cursor _cursor = __db.query(_statement);
-                try {
-                    final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
-                    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
-                    final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
-                    final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
-                    final User _result;
-                    if(_cursor.moveToFirst()) {
-                        _result = new User();
-                        _result.uid = _cursor.getInt(_cursorIndexOfUid);
-                        _result.name = _cursor.getString(_cursorIndexOfName);
-                        final String _tmpLastName;
-                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-                        _result.setLastName(_tmpLastName);
-                        _result.age = _cursor.getInt(_cursorIndexOfAge);
-                    } else {
-                        _result = null;
-                    }
-                    return _result;
-                } finally {
-                    _cursor.close();
-                }
-            }
+    @Query("SELECT * FROM user where uid IN (:ids)")
+    abstract public LiveData<List<User>> loadUsersByIdsLive(int... ids);
 
-            @Override
-            protected void finalize() {
-                _statement.release();
-            }
-        }.getLiveData();
-    }
-
-    @Override
-    public LiveData<List<User>> loadUsersByIdsLive(int... ids) {
-        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
-        _stringBuilder.append("SELECT * FROM user where uid IN (");
-        final int _inputSize = ids.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        final int _argCount = 0 + _inputSize;
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
-        int _argIndex = 1;
-        for (int _item : ids) {
-            _statement.bindLong(_argIndex, _item);
-            _argIndex ++;
-        }
-        return new ComputableLiveData<List<User>>() {
-            private Observer _observer;
-
-            @Override
-            protected List<User> compute() {
-                if (_observer == null) {
-                    _observer = new Observer("user") {
-                        @Override
-                        public void onInvalidated(@NonNull Set<String> tables) {
-                            invalidate();
-                        }
-                    };
-                    __db.getInvalidationTracker().addWeakObserver(_observer);
-                }
-                final Cursor _cursor = __db.query(_statement);
-                try {
-                    final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
-                    final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
-                    final int _cursorIndexOfLastName = _cursor.getColumnIndexOrThrow("lastName");
-                    final int _cursorIndexOfAge = _cursor.getColumnIndexOrThrow("ageColumn");
-                    final List<User> _result = new ArrayList<User>(_cursor.getCount());
-                    while(_cursor.moveToNext()) {
-                        final User _item_1;
-                        _item_1 = new User();
-                        _item_1.uid = _cursor.getInt(_cursorIndexOfUid);
-                        _item_1.name = _cursor.getString(_cursorIndexOfName);
-                        final String _tmpLastName;
-                        _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
-                        _item_1.setLastName(_tmpLastName);
-                        _item_1.age = _cursor.getInt(_cursorIndexOfAge);
-                        _result.add(_item_1);
-                    }
-                    return _result;
-                } finally {
-                    _cursor.close();
-                }
-            }
-
-            @Override
-            protected void finalize() {
-                _statement.release();
-            }
-        }.getLiveData();
-    }
-
-    @Override
-    public List<Integer> getAllAgesAsList(List<Integer> ids1, int[] ids2, int... ids3) {
-        StringBuilder _stringBuilder = StringUtil.newStringBuilder();
-        _stringBuilder.append("SELECT ageColumn FROM user where uid IN(");
-        final int _inputSize = ids1.size();
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-        _stringBuilder.append(") OR uid IN (");
-        final int _inputSize_1 = ids2.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_1);
-        _stringBuilder.append(") OR uid IN (");
-        final int _inputSize_2 = ids3.length;
-        StringUtil.appendPlaceholders(_stringBuilder, _inputSize_2);
-        _stringBuilder.append(")");
-        final String _sql = _stringBuilder.toString();
-        final int _argCount = 0 + _inputSize + _inputSize_1 + _inputSize_2;
-        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
-        int _argIndex = 1;
-        for (Integer _item : ids1) {
-            if (_item == null) {
-                _statement.bindNull(_argIndex);
-            } else {
-                _statement.bindLong(_argIndex, _item);
-            }
-            _argIndex ++;
-        }
-        _argIndex = 1 + _inputSize;
-        for (int _item_1 : ids2) {
-            _statement.bindLong(_argIndex, _item_1);
-            _argIndex ++;
-        }
-        _argIndex = 1 + _inputSize + _inputSize_1;
-        for (int _item_2 : ids3) {
-            _statement.bindLong(_argIndex, _item_2);
-            _argIndex ++;
-        }
-        final Cursor _cursor = __db.query(_statement);
-        try {
-            final List<Integer> _result = new ArrayList<Integer>(_cursor.getCount());
-            while(_cursor.moveToNext()) {
-                final Integer _item_3;
-                if (_cursor.isNull(0)) {
-                    _item_3 = null;
-                } else {
-                    _item_3 = _cursor.getInt(0);
-                }
-                _result.add(_item_3);
-            }
-            return _result;
-        } finally {
-            _cursor.close();
-            _statement.release();
-        }
-    }
+    @Query("SELECT ageColumn FROM user where uid IN(:ids1) OR uid IN (:ids2) OR uid IN (:ids3)")
+    abstract public List<Integer> getAllAgesAsList(List<Integer> ids1,
+            int[] ids2, int... ids3);
 }
diff --git a/foo/bar/ComplexDatabase.java b/foo/bar/ComplexDatabase.java
index cfdc110..f35e0b8 100644
--- a/foo/bar/ComplexDatabase.java
+++ b/foo/bar/ComplexDatabase.java
@@ -1,99 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package foo.bar;
-
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
-import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback;
-import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;
-import android.arch.persistence.room.DatabaseConfiguration;
-import android.arch.persistence.room.InvalidationTracker;
-import android.arch.persistence.room.RoomOpenHelper;
-import android.arch.persistence.room.RoomOpenHelper.Delegate;
-import android.arch.persistence.room.util.TableInfo;
-import android.arch.persistence.room.util.TableInfo.Column;
-import android.arch.persistence.room.util.TableInfo.ForeignKey;
-import android.arch.persistence.room.util.TableInfo.Index;
-import java.lang.IllegalStateException;
-import java.lang.Override;
-import java.lang.String;
-import java.util.HashMap;
-import java.util.HashSet;
-import javax.annotation.Generated;
-
-@Generated("android.arch.persistence.room.RoomProcessor")
-public class ComplexDatabase_Impl extends ComplexDatabase {
-    private volatile ComplexDao _complexDao;
-
-    protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
-        final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1923) {
-            public void createAllTables(SupportSQLiteDatabase _db) {
-                _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`uid` INTEGER NOT NULL, `name` TEXT, `lastName` TEXT, `ageColumn` INTEGER NOT NULL, PRIMARY KEY(`uid`))");
-                _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
-                _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6773601c5bcf94c71ee4eb0de04f21a4\")");
-            }
-
-            public void dropAllTables(SupportSQLiteDatabase _db) {
-                _db.execSQL("DROP TABLE IF EXISTS `User`");
-            }
-
-            protected void onCreate(SupportSQLiteDatabase _db) {
-                if (mCallbacks != null) {
-                    for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
-                        mCallbacks.get(_i).onCreate(_db);
-                    }
-                }
-            }
-
-            public void onOpen(SupportSQLiteDatabase _db) {
-                mDatabase = _db;
-                internalInitInvalidationTracker(_db);
-                if (mCallbacks != null) {
-                    for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
-                        mCallbacks.get(_i).onOpen(_db);
-                    }
-                }
-            }
-
-            protected void validateMigration(SupportSQLiteDatabase _db) {
-                final HashMap<String, TableInfo.Column> _columnsUser = new HashMap<String, TableInfo.Column>(4);
-                _columnsUser.put("uid", new TableInfo.Column("uid", "INTEGER", true, 1));
-                _columnsUser.put("name", new TableInfo.Column("name", "TEXT", false, 0));
-                _columnsUser.put("lastName", new TableInfo.Column("lastName", "TEXT", false, 0));
-                _columnsUser.put("ageColumn", new TableInfo.Column("ageColumn", "INTEGER", true, 0));
-                final HashSet<TableInfo.ForeignKey> _foreignKeysUser = new HashSet<TableInfo.ForeignKey>(0);
-                final HashSet<TableInfo.Index> _indicesUser = new HashSet<TableInfo.Index>(0);
-                final TableInfo _infoUser = new TableInfo("User", _columnsUser, _foreignKeysUser, _indicesUser);
-                final TableInfo _existingUser = TableInfo.read(_db, "User");
-                if (! _infoUser.equals(_existingUser)) {
-                    throw new IllegalStateException("Migration didn't properly handle User(foo.bar.User).\n"
-                            + " Expected:\n" + _infoUser + "\n"
-                            + " Found:\n" + _existingUser);
-                }
-            }
-        }, "6773601c5bcf94c71ee4eb0de04f21a4");
-        final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
-                .name(configuration.name)
-                .callback(_openCallback)
-                .build();
-        final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
-        return _helper;
-    }
-
-    @Override
-    protected InvalidationTracker createInvalidationTracker() {
-        return new InvalidationTracker(this, "User");
-    }
-
-    @Override
-    ComplexDao getComplexDao() {
-        if (_complexDao != null) {
-            return _complexDao;
-        } else {
-            synchronized(this) {
-                if(_complexDao == null) {
-                    _complexDao = new ComplexDao_Impl(this);
-                }
-                return _complexDao;
-            }
-        }
-    }
+import android.arch.persistence.room.*;
+import java.util.List;
+@Database(entities = {User.class}, version = 1923)
+abstract class ComplexDatabase extends RoomDatabase {
+    abstract ComplexDao getComplexDao();
 }
diff --git a/foo/bar/DeletionDao.java b/foo/bar/DeletionDao.java
index 067bf67..997f290 100644
--- a/foo/bar/DeletionDao.java
+++ b/foo/bar/DeletionDao.java
@@ -1,240 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package foo.bar;
-
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.SharedSQLiteStatement;
-import android.arch.persistence.room.util.StringUtil;
-import java.lang.Override;
-import java.lang.String;
-import java.lang.StringBuilder;
+import android.arch.persistence.room.*;
 import java.util.List;
-import javax.annotation.Generated;
 
-@Generated("android.arch.persistence.room.RoomProcessor")
-public class DeletionDao_Impl implements DeletionDao {
-  private final RoomDatabase __db;
+@Dao
+abstract interface DeletionDao {
+    @Delete
+    void deleteUser(User user);
+    @Delete
+    void deleteUsers(User user1, List<User> others);
+    @Delete
+    void deleteArrayOfUsers(User[] users);
 
-  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser;
+    @Delete
+    int deleteUserAndReturnCount(User user);
+    @Delete
+    int deleteUserAndReturnCount(User user1, List<User> others);
+    @Delete
+    int deleteUserAndReturnCount(User[] users);
 
-  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfMultiPKeyEntity;
+    @Delete
+    int multiPKey(MultiPKeyEntity entity);
 
-  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfBook;
+    @Query("DELETE FROM user where uid = :uid")
+    int deleteByUid(int uid);
 
-  private final SharedSQLiteStatement __preparedStmtOfDeleteByUid;
+    @Query("DELETE FROM user where uid IN(:uid)")
+    int deleteByUidList(int... uid);
 
-  private final SharedSQLiteStatement __preparedStmtOfDeleteEverything;
+    @Delete
+    void deleteUserAndBook(User user, Book book);
 
-  public DeletionDao_Impl(RoomDatabase __db) {
-    this.__db = __db;
-    this.__deletionAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-      @Override
-      public String createQuery() {
-        return "DELETE FROM `User` WHERE `uid` = ?";
-      }
-
-      @Override
-      public void bind(SupportSQLiteStatement stmt, User value) {
-        stmt.bindLong(1, value.uid);
-      }
-    };
-    this.__deletionAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
-      @Override
-      public String createQuery() {
-        return "DELETE FROM `MultiPKeyEntity` WHERE `name` = ? AND `lastName` = ?";
-      }
-
-      @Override
-      public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) {
-        if (value.name == null) {
-          stmt.bindNull(1);
-        } else {
-          stmt.bindString(1, value.name);
-        }
-        if (value.lastName == null) {
-          stmt.bindNull(2);
-        } else {
-          stmt.bindString(2, value.lastName);
-        }
-      }
-    };
-    this.__deletionAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
-      @Override
-      public String createQuery() {
-        return "DELETE FROM `Book` WHERE `bookId` = ?";
-      }
-
-      @Override
-      public void bind(SupportSQLiteStatement stmt, Book value) {
-        stmt.bindLong(1, value.bookId);
-      }
-    };
-    this.__preparedStmtOfDeleteByUid = new SharedSQLiteStatement(__db) {
-      @Override
-      public String createQuery() {
-        final String _query = "DELETE FROM user where uid = ?";
-        return _query;
-      }
-    };
-    this.__preparedStmtOfDeleteEverything = new SharedSQLiteStatement(__db) {
-      @Override
-      public String createQuery() {
-        final String _query = "DELETE FROM user";
-        return _query;
-      }
-    };
-  }
-
-  @Override
-  public void deleteUser(User user) {
-    __db.beginTransaction();
-    try {
-      __deletionAdapterOfUser.handle(user);
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public void deleteUsers(User user1, List<User> others) {
-    __db.beginTransaction();
-    try {
-      __deletionAdapterOfUser.handle(user1);
-      __deletionAdapterOfUser.handleMultiple(others);
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public void deleteArrayOfUsers(User[] users) {
-    __db.beginTransaction();
-    try {
-      __deletionAdapterOfUser.handleMultiple(users);
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public int deleteUserAndReturnCount(User user) {
-    int _total = 0;
-    __db.beginTransaction();
-    try {
-      _total +=__deletionAdapterOfUser.handle(user);
-      __db.setTransactionSuccessful();
-      return _total;
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public int deleteUserAndReturnCount(User user1, List<User> others) {
-    int _total = 0;
-    __db.beginTransaction();
-    try {
-      _total +=__deletionAdapterOfUser.handle(user1);
-      _total +=__deletionAdapterOfUser.handleMultiple(others);
-      __db.setTransactionSuccessful();
-      return _total;
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public int deleteUserAndReturnCount(User[] users) {
-    int _total = 0;
-    __db.beginTransaction();
-    try {
-      _total +=__deletionAdapterOfUser.handleMultiple(users);
-      __db.setTransactionSuccessful();
-      return _total;
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public int multiPKey(MultiPKeyEntity entity) {
-    int _total = 0;
-    __db.beginTransaction();
-    try {
-      _total +=__deletionAdapterOfMultiPKeyEntity.handle(entity);
-      __db.setTransactionSuccessful();
-      return _total;
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public void deleteUserAndBook(User user, Book book) {
-    __db.beginTransaction();
-    try {
-      __deletionAdapterOfUser.handle(user);
-      __deletionAdapterOfBook.handle(book);
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public int deleteByUid(int uid) {
-    final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteByUid.acquire();
-    __db.beginTransaction();
-    try {
-      int _argIndex = 1;
-      _stmt.bindLong(_argIndex, uid);
-      final int _result = _stmt.executeUpdateDelete();
-      __db.setTransactionSuccessful();
-      return _result;
-    } finally {
-      __db.endTransaction();
-      __preparedStmtOfDeleteByUid.release(_stmt);
-    }
-  }
-
-  @Override
-  public int deleteEverything() {
-    final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteEverything.acquire();
-    __db.beginTransaction();
-    try {
-      final int _result = _stmt.executeUpdateDelete();
-      __db.setTransactionSuccessful();
-      return _result;
-    } finally {
-      __db.endTransaction();
-      __preparedStmtOfDeleteEverything.release(_stmt);
-    }
-  }
-
-  @Override
-  public int deleteByUidList(int... uid) {
-    StringBuilder _stringBuilder = StringUtil.newStringBuilder();
-    _stringBuilder.append("DELETE FROM user where uid IN(");
-    final int _inputSize = uid.length;
-    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
-    _stringBuilder.append(")");
-    final String _sql = _stringBuilder.toString();
-    SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
-    int _argIndex = 1;
-    for (int _item : uid) {
-      _stmt.bindLong(_argIndex, _item);
-      _argIndex ++;
-    }
-    __db.beginTransaction();
-    try {
-      final int _result = _stmt.executeUpdateDelete();
-      __db.setTransactionSuccessful();
-      return _result;
-    } finally {
-      __db.endTransaction();
-    }
-  }
+    @Query("DELETE FROM user")
+    int deleteEverything();
 }
diff --git a/foo/bar/UpdateDao.java b/foo/bar/UpdateDao.java
index 1190a0d..040b5c7 100644
--- a/foo/bar/UpdateDao.java
+++ b/foo/bar/UpdateDao.java
@@ -1,240 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package foo.bar;
-
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.arch.persistence.room.EntityDeletionOrUpdateAdapter;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.SharedSQLiteStatement;
-import java.lang.Override;
-import java.lang.String;
+import android.arch.persistence.room.*;
 import java.util.List;
-import javax.annotation.Generated;
 
-@Generated("android.arch.persistence.room.RoomProcessor")
-public class UpdateDao_Impl implements UpdateDao {
-  private final RoomDatabase __db;
+@Dao
+abstract interface UpdateDao {
+    @Update
+    void updateUser(User user);
+    @Update
+    void updateUsers(User user1, List<User> others);
+    @Update
+    void updateArrayOfUsers(User[] users);
 
-  private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser;
+    @Update
+    int updateUserAndReturnCount(User user);
+    @Update
+    int updateUserAndReturnCount(User user1, List<User> others);
+    @Update
+    int updateUserAndReturnCount(User[] users);
 
-  private final EntityDeletionOrUpdateAdapter __updateAdapterOfMultiPKeyEntity;
+    @Update
+    int multiPKey(MultiPKeyEntity entity);
 
-  private final EntityDeletionOrUpdateAdapter __updateAdapterOfBook;
+    @Update
+    void updateUserAndBook(User user, Book book);
 
-  private final SharedSQLiteStatement __preparedStmtOfAgeUserByUid;
+    @Query("UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = :uid")
+    void ageUserByUid(String uid);
 
-  private final SharedSQLiteStatement __preparedStmtOfAgeUserAll;
-
-  public UpdateDao_Impl(RoomDatabase __db) {
-    this.__db = __db;
-    this.__updateAdapterOfUser = new EntityDeletionOrUpdateAdapter<User>(__db) {
-      @Override
-      public String createQuery() {
-        return "UPDATE OR ABORT `User` SET `uid` = ?,`name` = ?,`lastName` = ?,`ageColumn` = ? WHERE `uid` = ?";
-      }
-
-      @Override
-      public void bind(SupportSQLiteStatement stmt, User value) {
-        stmt.bindLong(1, value.uid);
-        if (value.name == null) {
-          stmt.bindNull(2);
-        } else {
-          stmt.bindString(2, value.name);
-        }
-        if (value.getLastName() == null) {
-          stmt.bindNull(3);
-        } else {
-          stmt.bindString(3, value.getLastName());
-        }
-        stmt.bindLong(4, value.age);
-        stmt.bindLong(5, value.uid);
-      }
-    };
-    this.__updateAdapterOfMultiPKeyEntity = new EntityDeletionOrUpdateAdapter<MultiPKeyEntity>(__db) {
-      @Override
-      public String createQuery() {
-        return "UPDATE OR ABORT `MultiPKeyEntity` SET `name` = ?,`lastName` = ? WHERE `name` = ? AND `lastName` = ?";
-      }
-
-      @Override
-      public void bind(SupportSQLiteStatement stmt, MultiPKeyEntity value) {
-        if (value.name == null) {
-          stmt.bindNull(1);
-        } else {
-          stmt.bindString(1, value.name);
-        }
-        if (value.lastName == null) {
-          stmt.bindNull(2);
-        } else {
-          stmt.bindString(2, value.lastName);
-        }
-        if (value.name == null) {
-          stmt.bindNull(3);
-        } else {
-          stmt.bindString(3, value.name);
-        }
-        if (value.lastName == null) {
-          stmt.bindNull(4);
-        } else {
-          stmt.bindString(4, value.lastName);
-        }
-      }
-    };
-    this.__updateAdapterOfBook = new EntityDeletionOrUpdateAdapter<Book>(__db) {
-      @Override
-      public String createQuery() {
-        return "UPDATE OR ABORT `Book` SET `bookId` = ?,`uid` = ? WHERE `bookId` = ?";
-      }
-
-      @Override
-      public void bind(SupportSQLiteStatement stmt, Book value) {
-        stmt.bindLong(1, value.bookId);
-        stmt.bindLong(2, value.uid);
-        stmt.bindLong(3, value.bookId);
-      }
-    };
-    this.__preparedStmtOfAgeUserByUid = new SharedSQLiteStatement(__db) {
-      @Override
-      public String createQuery() {
-        final String _query = "UPDATE User SET ageColumn = ageColumn + 1 WHERE uid = ?";
-        return _query;
-      }
-    };
-    this.__preparedStmtOfAgeUserAll = new SharedSQLiteStatement(__db) {
-      @Override
-      public String createQuery() {
-        final String _query = "UPDATE User SET ageColumn = ageColumn + 1";
-        return _query;
-      }
-    };
-  }
-
-  @Override
-  public void updateUser(User user) {
-    __db.beginTransaction();
-    try {
-      __updateAdapterOfUser.handle(user);
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public void updateUsers(User user1, List<User> others) {
-    __db.beginTransaction();
-    try {
-      __updateAdapterOfUser.handle(user1);
-      __updateAdapterOfUser.handleMultiple(others);
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public void updateArrayOfUsers(User[] users) {
-    __db.beginTransaction();
-    try {
-      __updateAdapterOfUser.handleMultiple(users);
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public int updateUserAndReturnCount(User user) {
-    int _total = 0;
-    __db.beginTransaction();
-    try {
-      _total +=__updateAdapterOfUser.handle(user);
-      __db.setTransactionSuccessful();
-      return _total;
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public int updateUserAndReturnCount(User user1, List<User> others) {
-    int _total = 0;
-    __db.beginTransaction();
-    try {
-      _total +=__updateAdapterOfUser.handle(user1);
-      _total +=__updateAdapterOfUser.handleMultiple(others);
-      __db.setTransactionSuccessful();
-      return _total;
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public int updateUserAndReturnCount(User[] users) {
-    int _total = 0;
-    __db.beginTransaction();
-    try {
-      _total +=__updateAdapterOfUser.handleMultiple(users);
-      __db.setTransactionSuccessful();
-      return _total;
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public int multiPKey(MultiPKeyEntity entity) {
-    int _total = 0;
-    __db.beginTransaction();
-    try {
-      _total +=__updateAdapterOfMultiPKeyEntity.handle(entity);
-      __db.setTransactionSuccessful();
-      return _total;
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public void updateUserAndBook(User user, Book book) {
-    __db.beginTransaction();
-    try {
-      __updateAdapterOfUser.handle(user);
-      __updateAdapterOfBook.handle(book);
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-    }
-  }
-
-  @Override
-  public void ageUserByUid(String uid) {
-    final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserByUid.acquire();
-    __db.beginTransaction();
-    try {
-      int _argIndex = 1;
-      if (uid == null) {
-        _stmt.bindNull(_argIndex);
-      } else {
-        _stmt.bindString(_argIndex, uid);
-      }
-      _stmt.executeUpdateDelete();
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-      __preparedStmtOfAgeUserByUid.release(_stmt);
-    }
-  }
-
-  @Override
-  public void ageUserAll() {
-    final SupportSQLiteStatement _stmt = __preparedStmtOfAgeUserAll.acquire();
-    __db.beginTransaction();
-    try {
-      _stmt.executeUpdateDelete();
-      __db.setTransactionSuccessful();
-    } finally {
-      __db.endTransaction();
-      __preparedStmtOfAgeUserAll.release(_stmt);
-    }
-  }
+    @Query("UPDATE User SET ageColumn = ageColumn + 1")
+    void ageUserAll();
 }
diff --git a/foo/bar/WriterDao.java b/foo/bar/WriterDao.java
index cfad046..e122479 100644
--- a/foo/bar/WriterDao.java
+++ b/foo/bar/WriterDao.java
@@ -15,131 +15,17 @@
  */
 
 package foo.bar;
-
-import android.arch.persistence.db.SupportSQLiteStatement;
-import android.arch.persistence.room.EntityInsertionAdapter;
-import android.arch.persistence.room.RoomDatabase;
-
-import java.lang.Override;
-import java.lang.String;
+import android.arch.persistence.room.*;
 import java.util.List;
-import javax.annotation.Generated;
 
-@Generated("android.arch.persistence.room.RoomProcessor")
-public class WriterDao_Impl implements WriterDao {
-    private final RoomDatabase __db;
-
-    private final EntityInsertionAdapter __insertionAdapterOfUser;
-
-    private final EntityInsertionAdapter __insertionAdapterOfUser_1;
-
-    private final EntityInsertionAdapter __insertionAdapterOfBook;
-
-    public WriterDao_Impl(RoomDatabase __db) {
-        this.__db = __db;
-        this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
-            @Override
-            public String createQuery() {
-                return "INSERT OR ABORT INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES"
-                        + " (?,?,?,?)";
-            }
-
-            @Override
-            public void bind(SupportSQLiteStatement stmt, User value) {
-                stmt.bindLong(1, value.uid);
-                if (value.name == null) {
-                    stmt.bindNull(2);
-                } else {
-                    stmt.bindString(2, value.name);
-                }
-                if (value.getLastName() == null) {
-                    stmt.bindNull(3);
-                } else {
-                    stmt.bindString(3, value.getLastName());
-                }
-                stmt.bindLong(4, value.age);
-            }
-        };
-        this.__insertionAdapterOfUser_1 = new EntityInsertionAdapter<User>(__db) {
-            @Override
-            public String createQuery() {
-                return "INSERT OR REPLACE INTO `User`(`uid`,`name`,`lastName`,`ageColumn`) VALUES"
-                        + " (?,?,?,?)";
-            }
-
-            @Override
-            public void bind(SupportSQLiteStatement stmt, User value) {
-                stmt.bindLong(1, value.uid);
-                if (value.name == null) {
-                    stmt.bindNull(2);
-                } else {
-                    stmt.bindString(2, value.name);
-                }
-                if (value.getLastName() == null) {
-                    stmt.bindNull(3);
-                } else {
-                    stmt.bindString(3, value.getLastName());
-                }
-                stmt.bindLong(4, value.age);
-            }
-        };
-        this.__insertionAdapterOfBook = new EntityInsertionAdapter<Book>(__db) {
-            @Override
-            public String createQuery() {
-                return "INSERT OR ABORT INTO `Book`(`bookId`,`uid`) VALUES (?,?)";
-            }
-
-            @Override
-            public void bind(SupportSQLiteStatement stmt, Book value) {
-                stmt.bindLong(1, value.bookId);
-                stmt.bindLong(2, value.uid);
-            }
-        };
-    }
-
-    @Override
-    public void insertUser(User user) {
-        __db.beginTransaction();
-        try {
-            __insertionAdapterOfUser.insert(user);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
-    }
-
-    @Override
-    public void insertUsers(User user1, List<User> others) {
-        __db.beginTransaction();
-        try {
-            __insertionAdapterOfUser.insert(user1);
-            __insertionAdapterOfUser.insert(others);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
-    }
-
-    @Override
-    public void insertUsers(User[] users) {
-        __db.beginTransaction();
-        try {
-            __insertionAdapterOfUser_1.insert(users);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
-    }
-
-    @Override
-    public void insertUserAndBook(User user, Book book) {
-        __db.beginTransaction();
-        try {
-            __insertionAdapterOfUser.insert(user);
-            __insertionAdapterOfBook.insert(book);
-            __db.setTransactionSuccessful();
-        } finally {
-            __db.endTransaction();
-        }
-    }
+@Dao
+abstract interface WriterDao {
+    @Insert
+    void insertUser(User user);
+    @Insert
+    void insertUsers(User user1, List<User> others);
+    @Insert(onConflict=OnConflictStrategy.REPLACE)
+    void insertUsers(User[] users);
+    @Insert
+    void insertUserAndBook(User user, Book book);
 }
diff --git a/java/io/RandomAccessFile.java b/java/io/RandomAccessFile.java
index df607cb..985f8ed 100644
--- a/java/io/RandomAccessFile.java
+++ b/java/io/RandomAccessFile.java
@@ -28,6 +28,7 @@
 
 import java.nio.channels.FileChannel;
 import sun.nio.ch.FileChannelImpl;
+import android.system.Os;
 import android.system.ErrnoException;
 import dalvik.system.CloseGuard;
 import libcore.io.IoBridge;
@@ -68,6 +69,12 @@
     // BEGIN Android-added: CloseGuard and some helper fields for Android changes in this file.
     private final CloseGuard guard = CloseGuard.get();
     private final byte[] scratch = new byte[8];
+
+    private static final int FLUSH_NONE = 0;
+    private static final int FLUSH_FSYNC = 1;
+    private static final int FLUSH_FDATASYNC = 2;
+    private int flushAfterWrite = FLUSH_NONE;
+
     private int mode;
     // END Android-added: CloseGuard and some helper fields for Android changes in this file.
 
@@ -230,9 +237,17 @@
             rw = true;
             if (mode.length() > 2) {
                 if (mode.equals("rws")) {
-                    imode |= O_SYNC;
+                    // Android-changed: For performance reasons, use fsync after each write.
+                    // RandomAccessFile.write may result in multiple write syscalls,
+                    // O_SYNC/O_DSYNC flags will cause a blocking wait on each syscall. Replacing
+                    // them with single fsync/fdatasync call gives better performance with only
+                    // minor decrease in reliability.
+                    // imode |= O_SYNC;
+                    flushAfterWrite = FLUSH_FSYNC;
                 } else if (mode.equals("rwd")) {
-                    imode |= O_DSYNC;
+                    // Android-changed: For performance reasons, use fdatasync after each write.
+                    // imode |= O_DSYNC;
+                    flushAfterWrite = FLUSH_FDATASYNC;
                 } else {
                     imode = -1;
                 }
@@ -267,10 +282,29 @@
 
         // BEGIN Android-changed: Use IoBridge.open() instead of open.
         fd = IoBridge.open(name, imode);
+        maybeSync();
         guard.open("close");
         // END Android-changed: Use IoBridge.open() instead of open.
     }
 
+    // BEGIN Android-added: Sync after rws/rwd write
+    private void maybeSync() {
+        if (flushAfterWrite == FLUSH_FSYNC) {
+            try {
+                fd.sync();
+            } catch (IOException e) {
+                // Ignored
+            }
+        } else if (flushAfterWrite == FLUSH_FDATASYNC) {
+            try {
+                Os.fdatasync(fd);
+            } catch (ErrnoException e) {
+                // Ignored
+            }
+        }
+    }
+    // END Android-added: Sync after rws/rwd write
+
     /**
      * Returns the opaque file descriptor object associated with this
      * stream.
@@ -329,7 +363,7 @@
      *                          end-of-file has been reached.
      */
     public int read() throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // Android-changed: Implement on top of libcore os API.
         // return read0();
         return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1;
     }
@@ -342,7 +376,7 @@
      * @exception IOException If an I/O error has occurred.
      */
     private int readBytes(byte b[], int off, int len) throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // Android-changed: Implement on top of libcore os API.
         ioTracker.trackIo(len, IoTracker.Mode.READ);
         return IoBridge.read(fd, b, off, len);
     }
@@ -485,10 +519,11 @@
      * @exception  IOException  if an I/O error occurs.
      */
     public void write(int b) throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         // write0(b);
         scratch[0] = (byte) (b & 0xff);
         write(scratch, 0, 1);
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     /**
@@ -500,9 +535,11 @@
      * @exception IOException If an I/O error has occurred.
      */
     private void writeBytes(byte b[], int off, int len) throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         ioTracker.trackIo(len, IoTracker.Mode.WRITE);
         IoBridge.write(fd, b, off, len);
+        maybeSync();
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     /**
@@ -539,12 +576,13 @@
      * @exception  IOException  if an I/O error occurs.
      */
     public long getFilePointer() throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         try {
             return Libcore.os.lseek(fd, 0L, SEEK_CUR);
         } catch (ErrnoException errnoException) {
             throw errnoException.rethrowAsIOException();
         }
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     /**
@@ -567,7 +605,7 @@
             // throw new IOException("Negative seek offset");
             throw new IOException("offset < 0: " + pos);
         } else {
-            // Android-changed: Implement on top of low-level API, not directly natively.
+            // BEGIN Android-changed: Implement on top of libcore os API.
             // seek0(pos);
             try {
                 Libcore.os.lseek(fd, pos, SEEK_SET);
@@ -575,6 +613,7 @@
             } catch (ErrnoException errnoException) {
                 throw errnoException.rethrowAsIOException();
             }
+            // END Android-changed: Implement on top of libcore os API.
         }
     }
 
@@ -585,12 +624,13 @@
      * @exception  IOException  if an I/O error occurs.
      */
     public long length() throws IOException {
-        // Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         try {
             return Libcore.os.fstat(fd).st_size;
         } catch (ErrnoException errnoException) {
             throw errnoException.rethrowAsIOException();
         }
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     /**
@@ -613,7 +653,7 @@
      * @since      1.2
      */
     public void setLength(long newLength) throws IOException {
-        // BEGIN Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         if (newLength < 0) {
             throw new IllegalArgumentException("newLength < 0");
         }
@@ -627,7 +667,8 @@
         if (filePointer > newLength) {
             seek(newLength);
         }
-        // END Android-changed: Implement on top of low-level API, not directly natively.
+        maybeSync();
+        // END Android-changed: Implement on top of libcore os API.
     }
 
 
@@ -654,12 +695,12 @@
             closed = true;
         }
 
-        // BEGIN Android-changed: Implement on top of low-level API, not directly natively.
+        // BEGIN Android-changed: Implement on top of libcore os API.
         if (channel != null && channel.isOpen()) {
             channel.close();
         }
         IoBridge.closeAndSignalBlockedThreads(fd);
-        // END Android-changed: Implement on top of low-level API, not directly natively.
+        // END Android-changed: Implement on top of libcore os API.
     }
 
     //
diff --git a/java/net/AbstractPlainSocketImpl.java b/java/net/AbstractPlainSocketImpl.java
index 85eac6f..e5b0301 100644
--- a/java/net/AbstractPlainSocketImpl.java
+++ b/java/net/AbstractPlainSocketImpl.java
@@ -557,7 +557,6 @@
                 // Also, close the CloseGuard when the #close is called.
                 if (!closePending) {
                     closePending = true;
-                    SocketTagger.get().untag(fd);
                     guard.close();
 
                     if (fdUseCount == 0) {
diff --git a/java/text/DecimalFormatSymbols.java b/java/text/DecimalFormatSymbols.java
index a9f11c8..59b8cdc 100644
--- a/java/text/DecimalFormatSymbols.java
+++ b/java/text/DecimalFormatSymbols.java
@@ -660,12 +660,12 @@
             values[0] = String.valueOf(localeData.decimalSeparator);
             values[1] = String.valueOf(localeData.groupingSeparator);
             values[2] = String.valueOf(localeData.patternSeparator);
-            values[3] = String.valueOf(localeData.percent);
+            values[3] = localeData.percent;
             values[4] = String.valueOf(localeData.zeroDigit);
             values[5] = "#";
             values[6] = localeData.minusSign;
             values[7] = localeData.exponentSeparator;
-            values[8] = String.valueOf(localeData.perMill);
+            values[8] = localeData.perMill;
             values[9] = localeData.infinity;
             values[10] = localeData.NaN;
             data[0] = values;
diff --git a/javax/crypto/Cipher.java b/javax/crypto/Cipher.java
index e3c266f..010587d 100644
--- a/javax/crypto/Cipher.java
+++ b/javax/crypto/Cipher.java
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2014 The Android Open Source Project
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2014, 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
@@ -227,6 +227,18 @@
 
 public class Cipher {
 
+    // Android-note: Android reimplements provider selection.
+    //
+    // Android uses different provider/impl selection code than upstream does.  Provider
+    // selection permeates much of this class, so this class is forked significantly
+    // from the upstream version.  Not every change is marked, and any changes to upstream code
+    // should be evaluated to see if they should be merged.
+    //
+    // The changes are chiefly in construction (constructors, getInstance, and createCipher) and
+    // initialization (init and chooseProvider).  Most of the actual implementation is in the
+    // classes and methods at the bottom of this file.
+
+
     // Android-removed: this debugging mechanism is not used in Android.
     /*
     private static final Debug debug =
@@ -280,11 +292,22 @@
     private CipherSpi spi;
 
     // The transformation
+    // Android-changed: Made final.
     final private String transformation;
 
+    // Android-added: Added tokenizedTransformation.
     // The tokenized version of transformation
     final private String[] tokenizedTransformation;
 
+    // Android-removed: Removed cryptoPerm.
+    /*
+    // Crypto permission representing the maximum allowable cryptographic
+    // strength that this Cipher object can be used for. (The cryptographic
+    // strength is a function of the keysize and algorithm parameters encoded
+    // in the crypto permission.)
+    private CryptoPermission cryptoPerm;
+    */
+
     // The exemption mechanism that needs to be enforced
     private ExemptionMechanism exmech;
 
@@ -298,8 +321,28 @@
     // The OID for the KeyUsage extension in an X.509 v3 certificate
     private static final String KEY_USAGE_EXTENSION_OID = "2.5.29.15";
 
+    // BEGIN Android-changed: Reimplement provider selection.
+    // See note at top of class.
     private final SpiAndProviderUpdater spiAndProviderUpdater;
+    /*
+    // next SPI  to try in provider selection
+    // null once provider is selected
+    private CipherSpi firstSpi;
 
+    // next service to try in provider selection
+    // null once provider is selected
+    private Service firstService;
+
+    // remaining services to try in provider selection
+    // null once provider is selected
+    private Iterator<Service> serviceIterator;
+
+    // list of transform Strings to lookup in the provider
+    private List<Transform> transforms;
+
+    private final Object lock;
+    */
+    // END Android-changed: Reimplement provider selection.
 
     /**
      * Creates a Cipher object.
@@ -376,6 +419,141 @@
         return parts;
     }
 
+    // BEGIN Android-removed: Reimplement provider selection.
+    // See note at top of class.
+    /*
+    // Provider attribute name for supported chaining mode
+    private final static String ATTR_MODE = "SupportedModes";
+    // Provider attribute name for supported padding names
+    private final static String ATTR_PAD  = "SupportedPaddings";
+
+    // constants indicating whether the provider supports
+    // a given mode or padding
+    private final static int S_NO    = 0;       // does not support
+    private final static int S_MAYBE = 1;       // unable to determine
+    private final static int S_YES   = 2;       // does support
+
+    /**
+     * Nested class to deal with modes and paddings.
+     *
+    private static class Transform {
+        // transform string to lookup in the provider
+        final String transform;
+        // the mode/padding suffix in upper case. for example, if the algorithm
+        // to lookup is "DES/CBC/PKCS5Padding" suffix is "/CBC/PKCS5PADDING"
+        // if loopup is "DES", suffix is the empty string
+        // needed because aliases prevent straight transform.equals()
+        final String suffix;
+        // value to pass to setMode() or null if no such call required
+        final String mode;
+        // value to pass to setPadding() or null if no such call required
+        final String pad;
+        Transform(String alg, String suffix, String mode, String pad) {
+            this.transform = alg + suffix;
+            this.suffix = suffix.toUpperCase(Locale.ENGLISH);
+            this.mode = mode;
+            this.pad = pad;
+        }
+        // set mode and padding for the given SPI
+        void setModePadding(CipherSpi spi) throws NoSuchAlgorithmException,
+                NoSuchPaddingException {
+            if (mode != null) {
+                spi.engineSetMode(mode);
+            }
+            if (pad != null) {
+                spi.engineSetPadding(pad);
+            }
+        }
+        // check whether the given services supports the mode and
+        // padding described by this Transform
+        int supportsModePadding(Service s) {
+            int smode = supportsMode(s);
+            if (smode == S_NO) {
+                return smode;
+            }
+            int spad = supportsPadding(s);
+            // our constants are defined so that Math.min() is a tri-valued AND
+            return Math.min(smode, spad);
+        }
+
+        // separate methods for mode and padding
+        // called directly by Cipher only to throw the correct exception
+        int supportsMode(Service s) {
+            return supports(s, ATTR_MODE, mode);
+        }
+        int supportsPadding(Service s) {
+            return supports(s, ATTR_PAD, pad);
+        }
+
+        private static int supports(Service s, String attrName, String value) {
+            if (value == null) {
+                return S_YES;
+            }
+            String regexp = s.getAttribute(attrName);
+            if (regexp == null) {
+                return S_MAYBE;
+            }
+            return matches(regexp, value) ? S_YES : S_NO;
+        }
+
+        // ConcurrentMap<String,Pattern> for previously compiled patterns
+        private final static ConcurrentMap<String, Pattern> patternCache =
+            new ConcurrentHashMap<String, Pattern>();
+
+        private static boolean matches(String regexp, String str) {
+            Pattern pattern = patternCache.get(regexp);
+            if (pattern == null) {
+                pattern = Pattern.compile(regexp);
+                patternCache.putIfAbsent(regexp, pattern);
+            }
+            return pattern.matcher(str.toUpperCase(Locale.ENGLISH)).matches();
+        }
+
+    }
+
+    private static List<Transform> getTransforms(String transformation)
+            throws NoSuchAlgorithmException {
+        String[] parts = tokenizeTransformation(transformation);
+
+        String alg = parts[0];
+        String mode = parts[1];
+        String pad = parts[2];
+        if ((mode != null) && (mode.length() == 0)) {
+            mode = null;
+        }
+        if ((pad != null) && (pad.length() == 0)) {
+            pad = null;
+        }
+
+        if ((mode == null) && (pad == null)) {
+            // DES
+            Transform tr = new Transform(alg, "", null, null);
+            return Collections.singletonList(tr);
+        } else { // if ((mode != null) && (pad != null)) {
+            // DES/CBC/PKCS5Padding
+            List<Transform> list = new ArrayList<>(4);
+            list.add(new Transform(alg, "/" + mode + "/" + pad, null, null));
+            list.add(new Transform(alg, "/" + mode, null, pad));
+            list.add(new Transform(alg, "//" + pad, mode, null));
+            list.add(new Transform(alg, "", mode, pad));
+            return list;
+        }
+    }
+
+    // get the transform matching the specified service
+    private static Transform getTransform(Service s,
+                                          List<Transform> transforms) {
+        String alg = s.getAlgorithm().toUpperCase(Locale.ENGLISH);
+        for (Transform tr : transforms) {
+            if (alg.endsWith(tr.suffix)) {
+                return tr;
+            }
+        }
+        return null;
+    }
+    */
+    // END Android-removed: Reimplement provider selection.
+
     /**
      * Returns a <code>Cipher</code> object that implements the specified
      * transformation.
@@ -466,7 +644,7 @@
             throw new NoSuchProviderException("No such provider: " +
                                               provider);
         }
-        return createCipher(transformation, p);
+        return getInstance(transformation, p);
     }
 
     /**
@@ -514,6 +692,7 @@
 
     static final Cipher createCipher(String transformation, Provider provider)
         throws NoSuchAlgorithmException, NoSuchPaddingException {
+        Providers.checkBouncyCastleDeprecation(provider, "Cipher", transformation);
         String[] tokenizedTransformation = tokenizeTransformation(transformation);
 
         CipherSpiAndProvider cipherSpiAndProvider = null;
@@ -693,6 +872,112 @@
         return exmech;
     }
 
+    // BEGIN Android-removed: Eliminate crypto permission checking.
+    // Android doesn't implement SecurityManager permissions.
+    /*
+    //
+    // Crypto permission check code below
+    //
+    private void checkCryptoPerm(CipherSpi checkSpi, Key key)
+            throws InvalidKeyException {
+        if (cryptoPerm == CryptoAllPermission.INSTANCE) {
+            return;
+        }
+        // Check if key size and default parameters are within legal limits
+        AlgorithmParameterSpec params;
+        try {
+            params = getAlgorithmParameterSpec(checkSpi.engineGetParameters());
+        } catch (InvalidParameterSpecException ipse) {
+            throw new InvalidKeyException
+                ("Unsupported default algorithm parameters");
+        }
+        if (!passCryptoPermCheck(checkSpi, key, params)) {
+            throw new InvalidKeyException(
+                "Illegal key size or default parameters");
+        }
+    }
+
+    private void checkCryptoPerm(CipherSpi checkSpi, Key key,
+            AlgorithmParameterSpec params) throws InvalidKeyException,
+            InvalidAlgorithmParameterException {
+        if (cryptoPerm == CryptoAllPermission.INSTANCE) {
+            return;
+        }
+        // Determine keysize and check if it is within legal limits
+        if (!passCryptoPermCheck(checkSpi, key, null)) {
+            throw new InvalidKeyException("Illegal key size");
+        }
+        if ((params != null) && (!passCryptoPermCheck(checkSpi, key, params))) {
+            throw new InvalidAlgorithmParameterException("Illegal parameters");
+        }
+    }
+
+    private void checkCryptoPerm(CipherSpi checkSpi, Key key,
+            AlgorithmParameters params)
+            throws InvalidKeyException, InvalidAlgorithmParameterException {
+        if (cryptoPerm == CryptoAllPermission.INSTANCE) {
+            return;
+        }
+        // Convert the specified parameters into specs and then delegate.
+        AlgorithmParameterSpec pSpec;
+        try {
+            pSpec = getAlgorithmParameterSpec(params);
+        } catch (InvalidParameterSpecException ipse) {
+            throw new InvalidAlgorithmParameterException
+                ("Failed to retrieve algorithm parameter specification");
+        }
+        checkCryptoPerm(checkSpi, key, pSpec);
+    }
+
+    private boolean passCryptoPermCheck(CipherSpi checkSpi, Key key,
+                                        AlgorithmParameterSpec params)
+            throws InvalidKeyException {
+        String em = cryptoPerm.getExemptionMechanism();
+        int keySize = checkSpi.engineGetKeySize(key);
+        // Use the "algorithm" component of the cipher
+        // transformation so that the perm check would
+        // work when the key has the "aliased" algo.
+        String algComponent;
+        int index = transformation.indexOf('/');
+        if (index != -1) {
+            algComponent = transformation.substring(0, index);
+        } else {
+            algComponent = transformation;
+        }
+        CryptoPermission checkPerm =
+            new CryptoPermission(algComponent, keySize, params, em);
+
+        if (!cryptoPerm.implies(checkPerm)) {
+            if (debug != null) {
+                debug.println("Crypto Permission check failed");
+                debug.println("granted: " + cryptoPerm);
+                debug.println("requesting: " + checkPerm);
+            }
+            return false;
+        }
+        if (exmech == null) {
+            return true;
+        }
+        try {
+            if (!exmech.isCryptoAllowed(key)) {
+                if (debug != null) {
+                    debug.println(exmech.getName() + " isn't enforced");
+                }
+                return false;
+            }
+        } catch (ExemptionMechanismException eme) {
+            if (debug != null) {
+                debug.println("Cannot determine whether "+
+                              exmech.getName() + " has been enforced");
+                eme.printStackTrace();
+            }
+            return false;
+        }
+        return true;
+    }
+    */
+    // END Android-removed: Eliminate crypto permission checking.
+
     // check if opmode is one of the defined constants
     // throw InvalidParameterExeption if not
     private static void checkOpmode(int opmode) {
@@ -836,6 +1121,7 @@
 
         initialized = true;
         this.opmode = opmode;
+
         // Android-removed: this debugging mechanism is not used in Android.
         /*
         if (!skipDebug && pdebug != null) {
@@ -1263,7 +1549,8 @@
      */
     public final void init(int opmode, Certificate certificate,
                            SecureRandom random)
-            throws InvalidKeyException {
+            throws InvalidKeyException
+    {
         initialized = false;
         checkOpmode(opmode);
 
@@ -1272,28 +1559,28 @@
         if (certificate instanceof java.security.cert.X509Certificate) {
             // Check whether the cert has a key usage extension
             // marked as a critical extension.
-            X509Certificate cert = (X509Certificate) certificate;
+            X509Certificate cert = (X509Certificate)certificate;
             Set<String> critSet = cert.getCriticalExtensionOIDs();
 
             if (critSet != null && !critSet.isEmpty()
-                    && critSet.contains(KEY_USAGE_EXTENSION_OID)) {
+                && critSet.contains(KEY_USAGE_EXTENSION_OID)) {
                 boolean[] keyUsageInfo = cert.getKeyUsage();
                 // keyUsageInfo[2] is for keyEncipherment;
                 // keyUsageInfo[3] is for dataEncipherment.
                 if ((keyUsageInfo != null) &&
-                        (((opmode == Cipher.ENCRYPT_MODE) &&
-                                (keyUsageInfo.length > 3) &&
-                                (keyUsageInfo[3] == false)) ||
-                                ((opmode == Cipher.WRAP_MODE) &&
-                                        (keyUsageInfo.length > 2) &&
-                                        (keyUsageInfo[2] == false)))) {
+                    (((opmode == Cipher.ENCRYPT_MODE) &&
+                      (keyUsageInfo.length > 3) &&
+                      (keyUsageInfo[3] == false)) ||
+                     ((opmode == Cipher.WRAP_MODE) &&
+                      (keyUsageInfo.length > 2) &&
+                      (keyUsageInfo[2] == false)))) {
                     throw new InvalidKeyException("Wrong key usage");
                 }
             }
         }
 
         PublicKey publicKey =
-                (certificate == null ? null : certificate.getPublicKey());
+            (certificate==null? null:certificate.getPublicKey());
 
         try {
             chooseProvider(InitType.KEY, opmode, (Key) publicKey, null, null, random);
@@ -2346,6 +2633,8 @@
         spi.engineUpdateAAD(src);
     }
 
+    // BEGIN Android-added: Bulk of the new provider implementation.
+    // See note at top of class.
     /**
      * Returns the {@code CipherSpi} backing this {@code Cipher} or {@code null} if no
      * {@code CipherSpi} is backing this {@code Cipher}.
@@ -2677,4 +2966,5 @@
         }
         return null;
     }
+    // END Android-added: Bulk of the new provider implementation.
 }